diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..56b362405 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "go build", + "type": "shell", + "group": "build", + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/aws/elb.go b/aws/elb.go index c9d8e7132..a602365c8 100644 --- a/aws/elb.go +++ b/aws/elb.go @@ -2,7 +2,6 @@ package aws import ( "fmt" - "math" "math/rand" "strconv" "time" @@ -49,35 +48,22 @@ func getECSSession() (*ecs.ECS, error) { } // CheckELBFlags - Helper function to check if the correct config flags are set to use ELBs -// We accept two possible configurations here - either eureka_lookup_elbv2_endpoint can be set, -// for automatic lookup, or eureka_elbv2_hostname, eureka_elbv2_port and eureka_elbv2_targetgroup can be set manually -// to avoid the 10-20s wait for lookups +// eureka_elbv2_hostname, eureka_elbv2_port and eureka_elbv2_targetgroup must be set manually func CheckELBFlags(service *bridge.Service) bool { isAws := service.Attrs["eureka_datacenterinfo_name"] != fargo.MyOwn - var hasExplicit bool - var useLookup bool + var hasFlags bool if service.Attrs["eureka_elbv2_hostname"] != "" && service.Attrs["eureka_elbv2_port"] != "" && service.Attrs["eureka_elbv2_targetgroup"] != "" { v, err := strconv.ParseUint(service.Attrs["eureka_elbv2_port"], 10, 16) if err != nil { log.Errorf("eureka_elbv2_port must be valid 16-bit unsigned int, was %v : %s", v, err) - hasExplicit = false + hasFlags = false } - hasExplicit = true - useLookup = true + hasFlags = true } - if service.Attrs["eureka_lookup_elbv2_endpoint"] != "" { - v, err := strconv.ParseBool(service.Attrs["eureka_lookup_elbv2_endpoint"]) - if err != nil { - log.Errorf("eureka_lookup_elbv2_endpoint must be valid boolean, was %v : %s", v, err) - useLookup = false - } - useLookup = v - } - - if (hasExplicit || useLookup) && isAws { + if hasFlags && isAws { return true } return false @@ -102,333 +88,6 @@ func GetUniqueID(instance fargo.Instance) string { return instance.HostName + "_" + strconv.Itoa(instance.Port) } -// Get Load balancer and target group using a service and cluster name (more efficient) -func getLoadBalancerFromService(serviceName string, clusterName string) (*elbv2.LoadBalancer, *elbv2.TargetGroup, error) { - - dsi := ecs.DescribeServicesInput{ - Cluster: &clusterName, - Services: []*string{&serviceName}, - } - - svc, err := getECSSession() - if err != nil { - return nil, nil, err - } - svc2, err := getSession() - if err != nil { - return nil, nil, err - } - - out, err := svc.DescribeServices(&dsi) - if err != nil || out == nil { - log.Errorf("An error occurred using DescribeServices: %s \n", err.Error()) - return nil, nil, err - } - if len(out.Services) == 0 || len(out.Services[0].LoadBalancers) == 0 { - hnb := HasNoLoadBalancer{message: "Load balancer not found. It possibly doesn't exist for this service."} - return nil, nil, hnb - } - tgArn := out.Services[0].LoadBalancers[0].TargetGroupArn - - // Get the target group listed for the service - dtgI := elbv2.DescribeTargetGroupsInput{ - TargetGroupArns: []*string{tgArn}, - } - out3, err := svc2.DescribeTargetGroups(&dtgI) - if err != nil || out3 == nil { - log.Errorf("An error occurred using DescribeTargetGroups: %s \n", err.Error()) - return nil, nil, err - } - - // Get the load balancer details - dlbI := elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: out3.TargetGroups[0].LoadBalancerArns, - } - out2, err := svc2.DescribeLoadBalancers(&dlbI) - if err != nil || out2 == nil { - log.Errorf("An error occurred using DescribeLoadBalancers: %s \n", err.Error()) - return nil, nil, err - } - - return out2.LoadBalancers[0], out3.TargetGroups[0], nil -} - -// Helper function to retrieve all target groups -func getAllTargetGroups(svc *elbv2.ELBV2) ([]*elbv2.DescribeTargetGroupsOutput, error) { - var tgs []*elbv2.DescribeTargetGroupsOutput - var e error - var mark *string - - // Get first page of groups - tg, e := getTargetGroupsPage(svc, mark) - - if e != nil { - return nil, e - } - tgs = append(tgs, tg) - mark = tg.NextMarker - - // Page through all remaining target groups generating a slice of DescribeTargetGroupOutputs - for mark != nil { - tg, e = getTargetGroupsPage(svc, mark) - tgs = append(tgs, tg) - mark = tg.NextMarker - if e != nil { - return nil, e - } - } - return tgs, e -} - -// Helper function to get a page of target groups -func getTargetGroupsPage(svc *elbv2.ELBV2, marker *string) (*elbv2.DescribeTargetGroupsOutput, error) { - params := &elbv2.DescribeTargetGroupsInput{ - PageSize: awssdk.Int64(400), - Marker: marker, - } - - tg, e := svc.DescribeTargetGroups(params) - - if e != nil { - log.Errorf("An error occurred using DescribeTargetGroups: %s \n", e.Error()) - return nil, e - } - return tg, nil -} - -// GetELBV2ForContainer returns an LoadBalancerRegistrationInfo struct with the load balancer DNS name and listener port for a given instanceId and port -// if an error occurs, or the target is not found, an empty LoadBalancerRegistrationInfo is returned. -// Pass it the instanceID for the docker host, and the the host port to lookup the associated ELB. -// -func GetELBV2ForContainer(containerID string, instanceID string, port int64, clusterName string, taskArn string, serviceName string) (lbinfo *LoadBalancerRegistrationInfo, err error) { - i := lookupValues{InstanceID: instanceID, Port: port, ClusterName: clusterName, TaskArn: taskArn, ServiceName: serviceName} - out, err := GetAndCache("container_"+containerID, i, getELBAndCacheDetails, gocache.NoExpiration) - if out == nil || err != nil { - return nil, err - } - ret, _ := out.(*LoadBalancerRegistrationInfo) - return ret, err -} - -// Lookup the service name from the ECS cluster and taskArn. A bit janky, but works. -// Would be nice if amazon just put the service name as a label on the container. -func lookupServiceName(clusterName string, taskArn string) string { - - log.Debugf("Looking up service with cluster: %s and taskArn: %s", clusterName, taskArn) - svc, err := getECSSession() - if err != nil { - log.Errorf("Unable to get ECS session: %s", err) - return "" - } - - dti := ecs.DescribeTasksInput{ - Cluster: &clusterName, - Tasks: []*string{&taskArn}, - } - - lsi := ecs.ListServicesInput{ - Cluster: &clusterName, - } - - var servicesList []*string - err = svc.ListServicesPages(&lsi, - func(page *ecs.ListServicesOutput, lastPage bool) bool { - for _, s := range page.ServiceArns { - servicesList = append(servicesList, s) - } - return !lastPage - }) - if err != nil || servicesList == nil { - log.Errorf("Error occurred using ListServicesPages: %s", err) - return "" - } - - dtout, err := svc.DescribeTasks(&dti) - if err != nil || dtout == nil || len(dtout.Tasks) == 0 { - log.Errorf("Error occurred using DescribeTasks: %s", err) - return "" - } - taskDefArn := dtout.Tasks[0].TaskDefinitionArn - log.Debugf("Task definition is: %v", *taskDefArn) - - // Lookup using maximum chunks of 10 service names - var matchedServices []*string - var servChunk []*string - for i := 0; i < len(servicesList); i++ { - - servChunk = append(servChunk, servicesList[i]) - // Make an API call every 10 service names, or on the last iteration over servicesList - if (math.Mod(float64(i+1), 10) == 0 && i > 0) || i == (len(servicesList)-1) { - dsi := ecs.DescribeServicesInput{ - Cluster: &clusterName, - Services: servChunk, - } - - dsout, err := svc.DescribeServices(&dsi) - if err != nil || dsout == nil { - log.Errorf("Error occurred using DescribeServices: %s", err) - return "" - } - for _, ser := range dsout.Services { - if *ser.TaskDefinition == *taskDefArn { - matchedServices = append(matchedServices, ser.ServiceName) - } - } - servChunk = nil - } - } - if len(matchedServices) == 1 { - return *matchedServices[0] - } - if len(matchedServices) > 1 { - log.Errorf("More than one service matches the task definition. Cannot use fast lookup.") - return "" - } - log.Errorf("Service could not be identified") - return "" -} - -// -// Does the real work of retrieving the load balancer details, given a lookupValues struct. -// Note: This function uses caching extensively to reduce the burden on the AWS API when called from multiple goroutines -// -func getELBAndCacheDetails(l lookupValues) (lbinfo *LoadBalancerRegistrationInfo, err error) { - instanceID := l.InstanceID - port := l.Port - - var lbArns []*string - var lbPort *int64 - var tgArn string - info := &LoadBalancerRegistrationInfo{} - var clusterName string - var serviceName string - - // Small random wait to reduce risk of throttling - seed := rand.NewSource(time.Now().UnixNano()) - r2 := rand.New(seed) - random := r2.Intn(5000) - period := time.Millisecond * time.Duration(random) - log.Debugf("Waiting for %v on ELBv2 lookup to avoid throttling.", period) - time.Sleep(period) - - svc, err := getSession() - if err != nil { - return nil, err - } - - // We've got a service name already from a label - if l.ServiceName != "" { - serviceName = l.ServiceName - } - - // We've got a clusterName and taskArn so we can lookup the service - if l.ClusterName != "" && l.TaskArn != "" && l.ServiceName == "" { - serviceName = lookupServiceName(l.ClusterName, l.TaskArn) - clusterName = l.ClusterName - } - - var tgslice []*elbv2.DescribeTargetGroupsOutput - if serviceName == "" { - // There could be thousands of these, and we need to check them all. - // much better to have a service name to use. - - out1, err := GetAndCache("tg", svc, getAllTargetGroups, DEFAULT_EXP_TIME) - if err != nil || out1 == nil { - message := fmt.Errorf("Failed to retrieve Target Groups: %s", err) - return nil, message - } - tgslice, _ = out1.([]*elbv2.DescribeTargetGroupsOutput) - - // Check each target group's target list for a matching port and instanceID - // Assumption: that that there is only one LB for the target group (though the data structure allows more) - for _, tgs := range tgslice { - log.Debugf("%v target groups to check.", len(tgs.TargetGroups)) - for _, tg := range tgs.TargetGroups { - - thParams := &elbv2.DescribeTargetHealthInput{ - TargetGroupArn: awssdk.String(*tg.TargetGroupArn), - } - - out2, err := GetAndCache("tg_arn_"+*thParams.TargetGroupArn, thParams, svc.DescribeTargetHealth, DEFAULT_EXP_TIME) - if err != nil || out2 == nil { - log.Errorf("An error occurred using DescribeTargetHealth: %s \n", err.Error()) - return nil, err - } - tarH, ok := out2.(*elbv2.DescribeTargetHealthOutput) - if !ok || tarH.TargetHealthDescriptions == nil { - continue - } - for _, thd := range tarH.TargetHealthDescriptions { - if *thd.Target.Port == port && *thd.Target.Id == instanceID { - log.Debugf("Target group matched - %v", *tg.TargetGroupArn) - lbArns = tg.LoadBalancerArns - tgArn = *tg.TargetGroupArn - break - } - } - } - if lbArns != nil && tgArn != "" { - break - } - } - - if err != nil || lbArns == nil { - message := fmt.Errorf("failed to retrieve load balancer ARN") - return nil, message - } - - } else { - // We have the service and cluster name to use - lb, tg, err := getLoadBalancerFromService(serviceName, clusterName) - if lb == nil || tg == nil || err != nil { - return nil, err - } - tgArn = *tg.TargetGroupArn - lbArns = []*string{lb.LoadBalancerArn} - } - - // Loop through the load balancer listeners to get the listener port for the target group - lsnrParams := &elbv2.DescribeListenersInput{ - LoadBalancerArn: lbArns[0], - } - out3, err := GetAndCache("lsnr_"+*lsnrParams.LoadBalancerArn, lsnrParams, svc.DescribeListeners, DEFAULT_EXP_TIME) - if err != nil || out3 == nil { - log.Errorf("An error occurred using DescribeListeners: %s \n", err.Error()) - return nil, err - } - lnrData, _ := out3.(*elbv2.DescribeListenersOutput) - for _, listener := range lnrData.Listeners { - for _, act := range listener.DefaultActions { - if *act.TargetGroupArn == tgArn { - log.Debugf("Found matching listener: %v", *listener.ListenerArn) - lbPort = listener.Port - break - } - } - } - if lbPort == nil { - message := fmt.Errorf("error: Unable to identify listener port for ELBv2") - return nil, message - } - - // Get more information on the load balancer to retrieve the DNSName - lbParams := &elbv2.DescribeLoadBalancersInput{ - LoadBalancerArns: lbArns, - } - out4, err := GetAndCache("lb_"+*lbParams.LoadBalancerArns[0], lbParams, svc.DescribeLoadBalancers, DEFAULT_EXP_TIME) - if err != nil || out4 == nil { - log.Errorf("An error occurred using DescribeLoadBalancers: %s \n", err.Error()) - return nil, err - } - lbData, _ := out4.(*elbv2.DescribeLoadBalancersOutput) - log.Debugf("LB Endpoint for Instance:%v Port:%v, Target Group:%v, is: %s:%s\n", instanceID, port, tgArn, *lbData.LoadBalancers[0].DNSName, strconv.FormatInt(*lbPort, 10)) - - info.DNSName = *lbData.LoadBalancers[0].DNSName - info.Port = int(*lbPort) - info.TargetGroupArn = tgArn - return info, nil -} - // Helper function to alter registration info and add the ELBv2 endpoint // useCache parameter is passed to getELBV2ForContainer func mutateRegistrationInfo(service *bridge.Service, registration *fargo.Instance) *fargo.Instance { @@ -469,7 +128,6 @@ func mutateRegistrationInfo(service *bridge.Service, registration *fargo.Instanc func getELBMetadata(service *bridge.Service, hostName string, port int) (LoadBalancerRegistrationInfo, error) { var elbMetadata LoadBalancerRegistrationInfo - awsMetadata := GetMetadata() // We've been given the ELB endpoint, so use this if service.Attrs["eureka_elbv2_hostname"] != "" && service.Attrs["eureka_elbv2_port"] != "" && service.Attrs["eureka_elbv2_targetgroup"] != "" { @@ -480,33 +138,6 @@ func getELBMetadata(service *bridge.Service, hostName string, port int) (LoadBal elbMetadata.ELBEndpoint = service.Attrs["eureka_elbv2_hostname"] + "_" + service.Attrs["eureka_elbv2_port"] elbMetadata.IpAddress = "" AddToCache("container_"+service.Origin.ContainerID, &elbMetadata, gocache.NoExpiration) - } else { - // We don't have the ELB endpoint, so look it up. - // Check for some ECS labels first, these will allow more efficient lookups - var clusterName string - var taskArn string - var serviceName string - - if service.Attrs["com.amazonaws.ecs.cluster"] != "" { - clusterName = service.Attrs["com.amazonaws.ecs.cluster"] - } - if service.Attrs["com.amazonaws.ecs.task-arn"] != "" { - taskArn = service.Attrs["com.amazonaws.ecs.task-arn"] - } - // This can be set manually with SERVICE_eureka_ecs_service for a more efficient lookup - amazon don't yet provide it. - if service.Attrs["ecs_service"] != "" { - serviceName = service.Attrs["ecs_service"] - } - - elbMetadata1, err := GetELBV2ForContainer(service.Origin.ContainerID, awsMetadata.InstanceID, int64(port), clusterName, taskArn, serviceName) - if err != nil || elbMetadata1 == nil { - log.Errorf("Unable to find associated ELBv2 for service: %s, instance: %s hostname: %s port: %v, Error: %s\n", service.Name, awsMetadata.InstanceID, hostName, port, err) - return elbMetadata, fmt.Errorf("No ELB data available") - } - elbMetadata = *elbMetadata1 - - elbMetadata.ELBEndpoint = elbMetadata.DNSName + "_" + strconv.Itoa(elbMetadata.Port) - elbMetadata.IpAddress = "" } return elbMetadata, nil } @@ -516,9 +147,9 @@ func getELBStatus(client fargo.EurekaConnection, registration *fargo.Instance) f result, err := client.GetInstance(registration.App, GetUniqueID(*registration)) if err != nil || result == nil { // Can't find the ELB, this is more than likely expected. It takes a short amount of time - // after a container launch, for a new service, for the ELB to be fully provisioned. + // after a container launch, for a new service, for the ELB to be fully provisioned. // This gets retried 3 times with the RegisterWithELBv2() method and an error is logged - // after each of those fail. + // after each of those fail. log.Warningf("ELB not yet present, or error retrieving from eureka: %s\n", err) return fargo.UNKNOWN } @@ -529,7 +160,7 @@ func getELBStatus(client fargo.EurekaConnection, registration *fargo.Instance) f // This will mean traffic is directed to the ALB rather than directly to containers func RegisterWithELBv2(service *bridge.Service, registration *fargo.Instance, client fargo.EurekaConnection) error { if CheckELBFlags(service) { - log.Debugf("Found ELBv2 flags, will attempt to register LB for: %s\n", GetUniqueID(*registration)) + log.Debugf("Found ELBv2 flags, will attempt to register load balancer for: %s\n", GetUniqueID(*registration)) elbReg := mutateRegistrationInfo(service, registration) if elbReg != nil { testHealth(service, client, elbReg) diff --git a/aws/elb_test.go b/aws/elb_test.go index 18b06a6de..ca682aa34 100644 --- a/aws/elb_test.go +++ b/aws/elb_test.go @@ -75,77 +75,18 @@ func setupCache(containerID string, instanceID string, lbDNSName string, contain fn2 := func(thds []*elbv2.TargetHealthDescription) ([]*elbv2.TargetHealthDescription, error) { return thds, nil } - GetAndCache("container_" + containerID, i, fn, gocache.NoExpiration) - GetAndCache("tg_arn_" + tgArn, thds, fn2, gocache.NoExpiration) + GetAndCache("container_"+containerID, i, fn, gocache.NoExpiration) + GetAndCache("tg_arn_"+tgArn, thds, fn2, gocache.NoExpiration) r, _ := generalCache.Get("container_" + containerID) fmt.Printf("Cache value now looks like this: %+v\n", r.(*LoadBalancerRegistrationInfo)) } - -// Test_GetELBV2ForContainer - Test expected values are returned -func Test_GetELBV2ForContainer(t *testing.T) { - - // Setup cache - lbWant := LoadBalancerRegistrationInfo{ - DNSName: "my-lb", - Port: 12345, - TargetGroupArn: "arn:1234", - } - - thds := []*elbv2.TargetHealthDescription{} - tgArn := "arn:1234" - setupCache("123123412", "instance-123", "my-lb", 1234, 12345, tgArn, thds) - - type args struct { - containerID string - instanceID string - cluster string - service string - task string - port int64 - } - tests := []struct { - name string - args args - wantLbinfo *LoadBalancerRegistrationInfo - wantErr bool - }{ - { - name: "should match", - args: args{containerID: "123123412", instanceID: "instance-123", port: int64(1234), cluster: "cluster", task: "task"}, - wantErr: false, - wantLbinfo: &lbWant, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotLbinfo, err := GetELBV2ForContainer(tt.args.containerID, tt.args.instanceID, tt.args.port, tt.args.cluster, tt.args.task, tt.args.service) - if (err != nil) != tt.wantErr { - t.Errorf("GetELBV2ForContainer() error = %+v, wantErr %+v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotLbinfo, tt.wantLbinfo) { - t.Errorf("GetELBV2ForContainer() = %+v, want %+v", gotLbinfo, tt.wantLbinfo) - } - }) - } -} - // TestCheckELBFlags - Test that ELBv2 flags are evaulated correctly func TestCheckELBFlags(t *testing.T) { - svcFalse := bridge.Service{ - Attrs: map[string]string{ - "eureka_lookup_elbv2_endpoint": "false", - "eureka_datacenterinfo_name": "AMAZON", - }, - } - svcFalse2 := bridge.Service{ Attrs: map[string]string{ - "eureka_lookup_elbv2_endpoint": "true", - "eureka_datacenterinfo_name": "MyOwn", + "eureka_datacenterinfo_name": "MyOwn", }, } @@ -164,13 +105,6 @@ func TestCheckELBFlags(t *testing.T) { }, } - svcTrue := bridge.Service{ - Attrs: map[string]string{ - "eureka_lookup_elbv2_endpoint": "true", - "eureka_datacenterinfo_name": "AMAZON", - }, - } - svcTrue2 := bridge.Service{ Attrs: map[string]string{ "eureka_elbv2_hostname": "my-name", @@ -180,26 +114,6 @@ func TestCheckELBFlags(t *testing.T) { }, } - svcTrue3 := bridge.Service{ - Attrs: map[string]string{ - "eureka_elbv2_hostname": "my-name", - "eureka_lookup_elbv2_endpoint": "true", - "eureka_elbv2_port": "1234", - "eureka_elbv2_targetgroup": "arn:1234", - "eureka_datacenterinfo_name": "AMAZON", - }, - } - - svcTrue4 := bridge.Service{ - Attrs: map[string]string{ - "eureka_elbv2_hostname": "my-name", - "eureka_lookup_elbv2_endpoint": "false", - "eureka_elbv2_port": "1234", - "eureka_elbv2_targetgroup": "arn:1234", - "eureka_datacenterinfo_name": "AMAZON", - }, - } - type args struct { service *bridge.Service } @@ -208,11 +122,6 @@ func TestCheckELBFlags(t *testing.T) { args args want bool }{ - { - name: "use lookup set to false", - args: args{service: &svcFalse}, - want: false, - }, { name: "datacentre is set to MyOwn", args: args{service: &svcFalse2}, @@ -228,26 +137,11 @@ func TestCheckELBFlags(t *testing.T) { args: args{service: &svcFalse4}, want: false, }, - { - name: "elb lookup set to true", - args: args{service: &svcTrue}, - want: true, - }, { name: "elb hostname and port are set", args: args{service: &svcTrue2}, want: true, }, - { - name: "elb hostname and port are set, as is lookup", - args: args{service: &svcTrue3}, - want: true, - }, - { - name: "elb hostname, and port are set, lookup is false", - args: args{service: &svcTrue4}, - want: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -258,109 +152,6 @@ func TestCheckELBFlags(t *testing.T) { } } -// Test_mutateRegistrationInfo - Test that registration struct is returned as expected -func Test_mutateRegistrationInfo(t *testing.T) { - initMetadata() // Used from metadata_test.go - - svc := bridge.Service{ - Attrs: map[string]string{ - "eureka_lookup_elbv2_endpoint": "true", - "eureka_datacenterinfo_name": "AMAZON", - }, - Name: "app", - Origin: bridge.ServicePort{ - ContainerID: "123123412", - }, - } - - awsInfo := eureka.AmazonMetadataType{ - PublicHostname: "i-should-not-be-used", - HostName: "i-should-not-be-used", - InstanceID: "i-should-not-be-used", - } - - dcInfo := eureka.DataCenterInfo{ - Name: eureka.Amazon, - Metadata: awsInfo, - } - - reg := eureka.Instance{ - DataCenterInfo: dcInfo, - Port: 5001, - IPAddr: "4.3.2.1", - App: "app", - VipAddress: "4.3.2.1", - HostName: "hostname_identifier", - Status: eureka.UP, - } - - thds := []*elbv2.TargetHealthDescription{} - tgArn := "arn:1234" - // Init LB info cache - setupCache("123123412", "instance-123", "correct-lb-dnsname", 1234, 9001, tgArn, thds) - - r, _ := generalCache.Get("container_123123412") - lb := r.(*LoadBalancerRegistrationInfo) - wantedAwsInfo := eureka.AmazonMetadataType{ - PublicHostname: lb.DNSName, - HostName: lb.DNSName, - InstanceID: lb.DNSName + "_" + strconv.Itoa(int(lb.Port)), - } - wantedDCInfo := eureka.DataCenterInfo{ - Name: eureka.Amazon, - Metadata: wantedAwsInfo, - } - wantedLeaseInfo := eureka.LeaseInfo{ - DurationInSecs: 35, - } - - wanted := eureka.Instance{ - DataCenterInfo: wantedDCInfo, - LeaseInfo: wantedLeaseInfo, - Port: int(lb.Port), - App: svc.Name, - IPAddr: "", - VipAddress: "", - HostName: lb.DNSName, - Status: eureka.UP, - } - - type args struct { - service *bridge.Service - registration *eureka.Instance - } - tests := []struct { - name string - args args - want *eureka.Instance - }{ - { - name: "Should match data", - args: args{service: &svc, registration: ®}, - want: &wanted, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := mutateRegistrationInfo(tt.args.service, tt.args.registration) - val := got.Metadata.GetMap()["has-elbv2"] - if val != "true" { - t.Errorf("mutateRegistrationInfo() = %+v, \n Wanted has-elbv2=true in metadata, was %+v", got, val) - } - val2 := got.Metadata.GetMap()["elbv2-endpoint"] - wantVal := lb.DNSName + "_" + strconv.Itoa(int(lb.Port)) - if val2 != wantVal { - t.Errorf("mutateRegistrationInfo() = %+v, \n Wanted elbv2-endpoint=%v in metadata, was %+v", got, wantVal, val) - } - //Overwrite metadata before comparing data structure - we've directly checked the flag we are looking for - got.Metadata = eureka.InstanceMetadata{} - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("mutateRegistrationInfo() = %+v, \nwant %+v\n", got, tt.want) - } - }) - } -} - // Test_mutateRegistrationInfoExplicitEndpoint - Test that registration struct is returned as expected when you set the host and port // and that they are used rather than the load balancer lookup func Test_mutateRegistrationInfoExplicitEndpoint(t *testing.T) { @@ -368,11 +159,10 @@ func Test_mutateRegistrationInfoExplicitEndpoint(t *testing.T) { svc := bridge.Service{ Attrs: map[string]string{ - "eureka_lookup_elbv2_endpoint": "false", - "eureka_elbv2_hostname": "hostname-i-set", - "eureka_elbv2_port": "65535", - "eureka_elbv2_targetgroup": "arn:1234", - "eureka_datacenterinfo_name": "AMAZON", + "eureka_elbv2_hostname": "hostname-i-set", + "eureka_elbv2_port": "65535", + "eureka_elbv2_targetgroup": "arn:1234", + "eureka_datacenterinfo_name": "AMAZON", }, Name: "app", Origin: bridge.ServicePort{ @@ -476,11 +266,10 @@ func Test_mutateRegistrationInfoExplicitEndpointCachesData(t *testing.T) { svc := bridge.Service{ Attrs: map[string]string{ - "eureka_lookup_elbv2_endpoint": "false", - "eureka_elbv2_hostname": "hostname-i-set", - "eureka_elbv2_port": "65535", - "eureka_elbv2_targetgroup": "arn:1234", - "eureka_datacenterinfo_name": "AMAZON", + "eureka_elbv2_hostname": "hostname-i-set", + "eureka_elbv2_port": "65535", + "eureka_elbv2_targetgroup": "arn:1234", + "eureka_datacenterinfo_name": "AMAZON", }, Name: "app", Origin: bridge.ServicePort{ @@ -489,11 +278,11 @@ func Test_mutateRegistrationInfoExplicitEndpointCachesData(t *testing.T) { } wantedLBDetails := &LoadBalancerRegistrationInfo{ - DNSName: "hostname-i-set", - IpAddress: "", - Port: 65535, + DNSName: "hostname-i-set", + IpAddress: "", + Port: 65535, TargetGroupArn: "arn:1234", - ELBEndpoint: "hostname-i-set_65535", + ELBEndpoint: "hostname-i-set_65535", } awsInfo := eureka.AmazonMetadataType{ @@ -527,7 +316,6 @@ func Test_mutateRegistrationInfoExplicitEndpointCachesData(t *testing.T) { tgArn := "arn:1234" setupTHDCache(tgArn, healthyTHDs) - t.Run("Should return UP and find LB value in cache", func(t *testing.T) { _ = mutateRegistrationInfo(&svc, ®) @@ -543,8 +331,6 @@ func Test_mutateRegistrationInfoExplicitEndpointCachesData(t *testing.T) { } - - // Test_mutateRegistrationInfoELBv2Only - Test that certain metadata is stripped out when using an ELBv2 only registration setting func Test_mutateRegistrationInfoELBv2Only(t *testing.T) { initMetadata() // Used from metadata_test.go @@ -552,7 +338,9 @@ func Test_mutateRegistrationInfoELBv2Only(t *testing.T) { svc := bridge.Service{ Attrs: map[string]string{ "eureka_elbv2_only_registration": "true", - "eureka_lookup_elbv2_endpoint": "false", + "eureka_elbv2_hostname": "hostname-i-set", + "eureka_elbv2_port": "65535", + "eureka_elbv2_targetgroup": "arn:1234", "eureka_datacenterinfo_name": "AMAZON", }, Name: "app", @@ -597,14 +385,14 @@ func Test_mutateRegistrationInfoELBv2Only(t *testing.T) { }, } // Force parsing of metadata - err, _ := reg.Metadata.GetString("elbv2-endpoint") - if err != "" { + _, err := reg.Metadata.GetString("elbv2-endpoint") + if err != nil { t.Errorf("Unable to parse metadata") } // Init LB info cache thds := []*elbv2.TargetHealthDescription{} tgArn := "arn:1234" - setupCache("123123412", "instance-123", "correct-hostname", 1234, 12345, tgArn, thds) + setupCache("123123412", "instance-123", "hostname-i-set", 1234, 65535, tgArn, thds) r, _ := generalCache.Get("container_123123412") lb := r.(*LoadBalancerRegistrationInfo) diff --git a/bridge/util.go b/bridge/util.go index 3ac67a827..d62fe1c5a 100644 --- a/bridge/util.go +++ b/bridge/util.go @@ -40,7 +40,7 @@ func SetIPLookupRetries(number int) { } // ShouldExitOnIPLookupFailure checks config if it should exit on ip failure. -func ShouldExitOnIPLookupFailure(b *Bridge) (bool) { +func ShouldExitOnIPLookupFailure(b *Bridge) bool { return b.config.ExitOnIPLookupFailure } @@ -59,7 +59,7 @@ func GetIPFromExternalSource() (string, bool) { } else { ip, err := ioutil.ReadAll(res.Body) if err != nil { - fail = fmt.Errorf("Failed to read body of lookup from external source. Attempting retry", err) + fail = fmt.Errorf("Failed to read body of lookup from external source. Attempting retry: %s", err.Error()) } else { log.Infof("Deferring to external source for IP address. Current IP is: %s", ip) _ip = ip diff --git a/docs/user/backends.md b/docs/user/backends.md index a2f02359d..f34ca9dc5 100644 --- a/docs/user/backends.md +++ b/docs/user/backends.md @@ -203,7 +203,6 @@ SERVICE_EUREKA_DATACENTERINFO_PUBLICIPV4 = Host IP (ignored if using automatic p SERVICE_EUREKA_DATACENTERINFO_LOCALIPV4 = Host or Container IP (depending on -internal flag, ignored if using automatic population) SERVICE_EUREKA_DATACENTERINFO_LOCALHOSTNAME = Host or Container IP (depending on -internal flag, ignored if using automatic population) SERVICE_EUREKA_REGISTER_AWS_PUBLIC_IP = false (if true, set VIP and IPADDR values to AWS public IP, ignored if NOT using automatic population) -SERVICE_EUREKA_LOOKUP_ELBV2_ENDPOINT = false (if true, an entry will be added for an ELBv2 connected to a container target group in ECS - see below for more details) SERVICE_EUREKA_ELBV2_HOSTNAME = If set, will explicitly be used as the ELBv2 hostname - see below section. SERVICE_EUREKA_ELBV2_PORT = If set, will be explicitly used as the ELBv2 Port - see below. SERVICE_EUREKA_ELBV2_TARGETGROUP = If set, will be explicitly used as the ELBv2 TargetGroup - see below. @@ -227,18 +226,6 @@ If you are using ECS (EC2 Container Service) and run containers in target groups - You will end up with multiple entries in eureka with the same endpoint, one for each container. HostName is still set to the container IP and port combo - Extra information added in metadata about being attached to the ELB; the `elbv2_endpoint` metadata and `has_elbv2` flag. -#### Automatic Lookups - -If you set the flag `SERVICE_EUREKA_LOOKUP_ELBV2_ENDPOINT=true` AND you have `SERVICE_EUREKA_DATACENTERINFO_NAME=Amazon` then this feature is enabled. - -It will attempt to connect to the AWS service using the IAM role of the container host. In ECS, this should just work. It will find the region associated with the container host, and connect using that region. - -#### Manual Endpoint Specification - -If you specify `SERVICE_EUREKA_ELBV2_HOSTNAME=`, `SERVICE_EUREKA_ELBV2_PORT=` and `SERVICE_EUREKA_ELBV2_TARGETGROUP=` values on the container, then these will be used, rather than a lookup attempted. - -If you specify the lookup flag, and also add ALL of these settings, the manual ones will take precedent. - ### AWS ELBv2 Only Registration If the Amazon Datacenter type is used, the default is for `SERVICE_EUREKA_ELBv2_ONLY_REGISTRATION` - to be true. This works in line with the other ELBv2 options, which also must be set appropriately. @@ -247,7 +234,16 @@ If the Amazon Datacenter type is used, the default is for `SERVICE_EUREKA_ELBv2_ - Each time a new container associated with the ELB endpoint is started, registrator will send a `Reregister` to eureka, updating the metadata. - During deploys, the metadata will be updated once for each new container to start. As such, the newest metadata always wins. - Heartbeats are still piggybacked onto container lifecycles. As such, heartbeats will be sent to a given ELB endpoint as many times as there are associated containers running. It would be possible to alter `--ttl` and `-ttl-refresh registrator` startup options to compensate and reduce the number of heartbeats if desired. -- A ELBv2 will not be explicitly removed once registered with eureka. Heartbeats will simply stop, as and when associated containers die. Once all associated containers stop heartbeating (that would be all within an ECS target group), the record would expire based on the `--ttl` registrator startup value. +- An ELBv2 will not be explicitly removed once registered with eureka. Heartbeats will simply stop, as and when associated containers die. Once all associated containers stop heartbeating (that would be all within an ECS target group), the record would expire based on the `--ttl` registrator startup value. + +#### ELB Endpoint Specification + +When using ELB registrations, you need to set the following on the container: +`SERVICE_EUREKA_ELBV2_HOSTNAME=`, +`SERVICE_EUREKA_ELBV2_PORT=` +`SERVICE_EUREKA_ELBV2_TARGETGROUP=` + +These will ensure the endpoint is correctly registed in eureka. If they are not all set correctly, then the container will be registered directly instead. #### IAM Policy In order for this to work (you will receive a log error if not) the IAM role attached to the ECS host must have something like the following additional policy: diff --git a/service-config/service.yaml b/service-config/service.yaml index 43679b23e..a1a32ae56 100644 --- a/service-config/service.yaml +++ b/service-config/service.yaml @@ -7,7 +7,6 @@ infrastructure: web: preferences: environment_variables: - - SERVICE_EUREKA_lookup_elbv2_endpoint: "false" - SERVICE_REGISTER: "false" - REGISTRATOR_LOG_LEVEL: "INFO" - FARGO_LOG_LEVEL: "NOTICE"