traefik/vendor/github.com/containous/traefik-extra-service-fabric/servicefabric.go

307 lines
8.9 KiB
Go
Raw Normal View History

2017-11-27 13:26:04 +00:00
package servicefabric
import (
"encoding/json"
"errors"
2017-11-27 13:26:04 +00:00
"net/http"
"strings"
2017-11-27 13:26:04 +00:00
"time"
"github.com/cenk/backoff"
"github.com/containous/flaeg"
2017-11-27 13:26:04 +00:00
"github.com/containous/traefik/job"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
2018-03-22 16:42:03 +00:00
"github.com/containous/traefik/provider/label"
2017-11-27 13:26:04 +00:00
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/jjcollinge/logrus-appinsights"
2017-11-27 13:26:04 +00:00
sf "github.com/jjcollinge/servicefabric"
)
var _ provider.Provider = (*Provider)(nil)
2018-03-22 16:42:03 +00:00
const traefikServiceFabricExtensionKey = "Traefik"
2017-11-27 13:26:04 +00:00
const (
kindStateful = "Stateful"
kindStateless = "Stateless"
)
2017-11-27 13:26:04 +00:00
// Provider holds for configuration for the provider
type Provider struct {
provider.BaseProvider `mapstructure:",squash"`
ClusterManagementURL string `description:"Service Fabric API endpoint"`
APIVersion string `description:"Service Fabric API version" export:"true"`
RefreshSeconds flaeg.Duration `description:"Polling interval (in seconds)" export:"true"`
2017-11-27 13:26:04 +00:00
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
AppInsightsClientName string `description:"The client name, Identifies the cloud instance"`
AppInsightsKey string `description:"Application Insights Instrumentation Key"`
AppInsightsBatchSize int `description:"Number of trace lines per batch, optional"`
AppInsightsInterval flaeg.Duration `description:"The interval for sending data to Application Insights, optional"`
2018-07-13 16:04:03 +00:00
sfClient sfClient
}
// Init the provider
func (p *Provider) Init(constraints types.Constraints) error {
2018-07-13 16:04:03 +00:00
err := p.BaseProvider.Init(constraints)
if err != nil {
return err
}
2017-11-27 13:26:04 +00:00
if p.APIVersion == "" {
p.APIVersion = sf.DefaultAPIVersion
}
tlsConfig, err := p.TLS.CreateTLSConfig()
if err != nil {
return err
}
2018-07-13 16:04:03 +00:00
p.sfClient, err = sf.NewClient(http.DefaultClient, p.ClusterManagementURL, p.APIVersion, tlsConfig)
2017-11-27 13:26:04 +00:00
if err != nil {
return err
}
if p.RefreshSeconds <= 0 {
p.RefreshSeconds = flaeg.Duration(10 * time.Second)
}
if p.AppInsightsClientName != "" && p.AppInsightsKey != "" {
if p.AppInsightsBatchSize == 0 {
p.AppInsightsBatchSize = 10
}
if p.AppInsightsInterval == 0 {
p.AppInsightsInterval = flaeg.Duration(5 * time.Second)
}
createAppInsightsHook(p.AppInsightsClientName, p.AppInsightsKey, p.AppInsightsBatchSize, p.AppInsightsInterval)
2017-11-27 13:26:04 +00:00
}
2018-07-13 16:04:03 +00:00
return nil
}
2017-11-27 13:26:04 +00:00
2018-07-13 16:04:03 +00:00
// Provide allows the ServiceFabric provider to provide configurations to traefik
// using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
return p.updateConfig(configurationChan, pool, time.Duration(p.RefreshSeconds))
2017-11-27 13:26:04 +00:00
}
2018-07-13 16:04:03 +00:00
func (p *Provider) updateConfig(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, pollInterval time.Duration) error {
2017-11-27 13:26:04 +00:00
pool.Go(func(stop chan bool) {
operation := func() error {
ticker := time.NewTicker(pollInterval)
for range ticker.C {
select {
case shouldStop := <-stop:
if shouldStop {
ticker.Stop()
return nil
}
default:
log.Info("Checking service fabric config")
}
2018-07-13 16:04:03 +00:00
configuration, err := p.getConfiguration()
2017-11-27 13:26:04 +00:00
if err != nil {
return err
}
configurationChan <- types.ConfigMessage{
ProviderName: "servicefabric",
Configuration: configuration,
}
}
return nil
}
notify := func(err error, time time.Duration) {
log.Errorf("Provider connection error: %v; retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
log.Errorf("Cannot connect to Provider: %v", err)
}
})
return nil
}
2018-07-13 16:04:03 +00:00
func (p *Provider) getConfiguration() (*types.Configuration, error) {
services, err := getClusterServices(p.sfClient)
if err != nil {
return nil, err
}
return p.buildConfiguration(services)
}
2017-11-27 13:26:04 +00:00
func getClusterServices(sfClient sfClient) ([]ServiceItemExtended, error) {
apps, err := sfClient.GetApplications()
if err != nil {
return nil, err
}
var results []ServiceItemExtended
for _, app := range apps.Items {
services, err := sfClient.GetServices(app.ID)
if err != nil {
return nil, err
}
for _, service := range services.Items {
item := ServiceItemExtended{
ServiceItem: service,
Application: app,
}
2018-03-22 16:42:03 +00:00
if labels, err := getLabels(sfClient, &service, &app); err != nil {
2017-11-27 13:26:04 +00:00
log.Error(err)
} else {
item.Labels = labels
}
if partitions, err := sfClient.GetPartitions(app.ID, service.ID); err != nil {
log.Error(err)
} else {
for _, partition := range partitions.Items {
partitionExt := PartitionItemExtended{PartitionItem: partition}
2018-03-22 16:42:03 +00:00
if isStateful(item) {
2017-11-27 13:26:04 +00:00
partitionExt.Replicas = getValidReplicas(sfClient, app, service, partition)
2018-03-22 16:42:03 +00:00
} else if isStateless(item) {
2017-11-27 13:26:04 +00:00
partitionExt.Instances = getValidInstances(sfClient, app, service, partition)
} else {
log.Errorf("Unsupported service kind %s in service %s", partition.ServiceKind, service.Name)
continue
}
item.Partitions = append(item.Partitions, partitionExt)
}
}
results = append(results, item)
}
}
return results, nil
}
func getValidReplicas(sfClient sfClient, app sf.ApplicationItem, service sf.ServiceItem, partition sf.PartitionItem) []sf.ReplicaItem {
var validReplicas []sf.ReplicaItem
if replicas, err := sfClient.GetReplicas(app.ID, service.ID, partition.PartitionInformation.ID); err != nil {
log.Error(err)
} else {
for _, instance := range replicas.Items {
if isHealthy(instance.ReplicaItemBase) && hasHTTPEndpoint(instance.ReplicaItemBase) {
validReplicas = append(validReplicas, instance)
}
}
}
return validReplicas
}
func getValidInstances(sfClient sfClient, app sf.ApplicationItem, service sf.ServiceItem, partition sf.PartitionItem) []sf.InstanceItem {
var validInstances []sf.InstanceItem
if instances, err := sfClient.GetInstances(app.ID, service.ID, partition.PartitionInformation.ID); err != nil {
log.Error(err)
} else {
for _, instance := range instances.Items {
if isHealthy(instance.ReplicaItemBase) && hasHTTPEndpoint(instance.ReplicaItemBase) {
validInstances = append(validInstances, instance)
}
}
}
return validInstances
}
func isHealthy(instanceData *sf.ReplicaItemBase) bool {
2017-12-18 09:30:08 +00:00
return instanceData != nil && (instanceData.ReplicaStatus == "Ready" && instanceData.HealthState != "Error")
2017-11-27 13:26:04 +00:00
}
func hasHTTPEndpoint(instanceData *sf.ReplicaItemBase) bool {
_, err := getReplicaDefaultEndpoint(instanceData)
2017-11-27 13:26:04 +00:00
return err == nil
}
func getReplicaDefaultEndpoint(replicaData *sf.ReplicaItemBase) (string, error) {
endpoints, err := decodeEndpointData(replicaData.Address)
if err != nil {
return "", err
}
var defaultHTTPEndpoint string
for _, v := range endpoints {
if strings.Contains(v, "http") {
defaultHTTPEndpoint = v
break
}
}
if len(defaultHTTPEndpoint) == 0 {
return "", errors.New("no default endpoint found")
}
return defaultHTTPEndpoint, nil
}
func decodeEndpointData(endpointData string) (map[string]string, error) {
var endpointsMap map[string]map[string]string
if endpointData == "" {
return nil, errors.New("endpoint data is empty")
}
err := json.Unmarshal([]byte(endpointData), &endpointsMap)
if err != nil {
return nil, err
}
endpoints, endpointsExist := endpointsMap["Endpoints"]
if !endpointsExist {
return nil, errors.New("endpoint doesn't exist in endpoint data")
}
return endpoints, nil
}
func isStateful(service ServiceItemExtended) bool {
return service.ServiceKind == kindStateful
}
func isStateless(service ServiceItemExtended) bool {
return service.ServiceKind == kindStateless
}
2018-03-22 16:42:03 +00:00
// Return a set of labels from the Extension and Property manager
// Allow Extension labels to disable importing labels from the property manager.
func getLabels(sfClient sfClient, service *sf.ServiceItem, app *sf.ApplicationItem) (map[string]string, error) {
labels, err := sfClient.GetServiceExtensionMap(service, app, traefikServiceFabricExtensionKey)
2017-11-27 13:26:04 +00:00
if err != nil {
2018-03-22 16:42:03 +00:00
log.Errorf("Error retrieving serviceExtensionMap: %v", err)
2017-11-27 13:26:04 +00:00
return nil, err
}
2018-03-22 16:42:03 +00:00
if label.GetBoolValue(labels, traefikSFEnableLabelOverrides, traefikSFEnableLabelOverridesDefault) {
if exists, properties, err := sfClient.GetProperties(service.ID); err == nil && exists {
for key, value := range properties {
labels[key] = value
}
}
2017-11-27 13:26:04 +00:00
}
2018-03-22 16:42:03 +00:00
return labels, nil
2017-11-27 13:26:04 +00:00
}
func createAppInsightsHook(appInsightsClientName string, instrumentationKey string, maxBatchSize int, interval flaeg.Duration) {
hook, err := logrus_appinsights.New(appInsightsClientName, logrus_appinsights.Config{
InstrumentationKey: instrumentationKey,
MaxBatchSize: maxBatchSize, // optional
MaxBatchInterval: time.Duration(interval), // optional
})
if err != nil || hook == nil {
panic(err)
}
// ignore fields
hook.AddIgnore("private")
log.AddHook(hook)
}