sigs.k8s.io/external-dns@v0.14.1/provider/awssd/aws_sd_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package awssd
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"math/rand"
    23  	"reflect"
    24  	"strconv"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/aws/aws-sdk-go/aws"
    29  	"github.com/aws/aws-sdk-go/aws/request"
    30  	sd "github.com/aws/aws-sdk-go/service/servicediscovery"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  
    34  	"sigs.k8s.io/external-dns/endpoint"
    35  	"sigs.k8s.io/external-dns/internal/testutils"
    36  	"sigs.k8s.io/external-dns/plan"
    37  )
    38  
    39  // Compile time check for interface conformance
    40  var _ AWSSDClient = &AWSSDClientStub{}
    41  
    42  var (
    43  	ErrNamespaceNotFound = errors.New("Namespace not found")
    44  )
    45  
    46  type AWSSDClientStub struct {
    47  	// map[namespace_id]namespace
    48  	namespaces map[string]*sd.Namespace
    49  
    50  	// map[namespace_id] => map[service_id]instance
    51  	services map[string]map[string]*sd.Service
    52  
    53  	// map[service_id] => map[inst_id]instance
    54  	instances map[string]map[string]*sd.Instance
    55  }
    56  
    57  func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error) {
    58  	srv := &sd.Service{
    59  		Id:               aws.String(strconv.Itoa(rand.Intn(10000))),
    60  		DnsConfig:        input.DnsConfig,
    61  		Name:             input.Name,
    62  		Description:      input.Description,
    63  		CreateDate:       aws.Time(time.Now()),
    64  		CreatorRequestId: input.CreatorRequestId,
    65  	}
    66  
    67  	nsServices, ok := s.services[*input.NamespaceId]
    68  	if !ok {
    69  		nsServices = make(map[string]*sd.Service)
    70  		s.services[*input.NamespaceId] = nsServices
    71  	}
    72  	nsServices[*srv.Id] = srv
    73  
    74  	return &sd.CreateServiceOutput{
    75  		Service: srv,
    76  	}, nil
    77  }
    78  
    79  func (s *AWSSDClientStub) DeregisterInstance(input *sd.DeregisterInstanceInput) (*sd.DeregisterInstanceOutput, error) {
    80  	serviceInstances := s.instances[*input.ServiceId]
    81  	delete(serviceInstances, *input.InstanceId)
    82  
    83  	return &sd.DeregisterInstanceOutput{}, nil
    84  }
    85  
    86  func (s *AWSSDClientStub) GetService(input *sd.GetServiceInput) (*sd.GetServiceOutput, error) {
    87  	for _, entry := range s.services {
    88  		srv, ok := entry[*input.Id]
    89  		if ok {
    90  			return &sd.GetServiceOutput{
    91  				Service: srv,
    92  			}, nil
    93  		}
    94  	}
    95  
    96  	return nil, errors.New("service not found")
    97  }
    98  
    99  func (s *AWSSDClientStub) DiscoverInstancesWithContext(ctx context.Context, input *sd.DiscoverInstancesInput, opts ...request.Option) (*sd.DiscoverInstancesOutput, error) {
   100  	instances := make([]*sd.HttpInstanceSummary, 0)
   101  
   102  	var foundNs bool
   103  	for _, ns := range s.namespaces {
   104  		if aws.StringValue(ns.Name) == aws.StringValue(input.NamespaceName) {
   105  			foundNs = true
   106  
   107  			for _, srv := range s.services[*ns.Id] {
   108  				if aws.StringValue(srv.Name) == aws.StringValue(input.ServiceName) {
   109  					for _, inst := range s.instances[*srv.Id] {
   110  						instances = append(instances, instanceToHTTPInstanceSummary(inst))
   111  					}
   112  				}
   113  			}
   114  		}
   115  	}
   116  
   117  	if !foundNs {
   118  		return nil, ErrNamespaceNotFound
   119  	}
   120  
   121  	return &sd.DiscoverInstancesOutput{
   122  		Instances: instances,
   123  	}, nil
   124  }
   125  
   126  func (s *AWSSDClientStub) ListNamespacesPages(input *sd.ListNamespacesInput, fn func(*sd.ListNamespacesOutput, bool) bool) error {
   127  	namespaces := make([]*sd.NamespaceSummary, 0)
   128  
   129  	filter := input.Filters[0]
   130  
   131  	for _, ns := range s.namespaces {
   132  		if filter != nil && *filter.Name == sd.NamespaceFilterNameType {
   133  			if *ns.Type != *filter.Values[0] {
   134  				// skip namespaces not matching filter
   135  				continue
   136  			}
   137  		}
   138  		namespaces = append(namespaces, namespaceToNamespaceSummary(ns))
   139  	}
   140  
   141  	fn(&sd.ListNamespacesOutput{
   142  		Namespaces: namespaces,
   143  	}, true)
   144  
   145  	return nil
   146  }
   147  
   148  func (s *AWSSDClientStub) ListServicesPages(input *sd.ListServicesInput, fn func(*sd.ListServicesOutput, bool) bool) error {
   149  	services := make([]*sd.ServiceSummary, 0)
   150  
   151  	// get namespace filter
   152  	filter := input.Filters[0]
   153  	if filter == nil || *filter.Name != sd.ServiceFilterNameNamespaceId {
   154  		return errors.New("missing namespace filter")
   155  	}
   156  	nsID := filter.Values[0]
   157  
   158  	for _, srv := range s.services[*nsID] {
   159  		services = append(services, serviceToServiceSummary(srv))
   160  	}
   161  
   162  	fn(&sd.ListServicesOutput{
   163  		Services: services,
   164  	}, true)
   165  
   166  	return nil
   167  }
   168  
   169  func (s *AWSSDClientStub) RegisterInstance(input *sd.RegisterInstanceInput) (*sd.RegisterInstanceOutput, error) {
   170  	srvInstances, ok := s.instances[*input.ServiceId]
   171  	if !ok {
   172  		srvInstances = make(map[string]*sd.Instance)
   173  		s.instances[*input.ServiceId] = srvInstances
   174  	}
   175  
   176  	srvInstances[*input.InstanceId] = &sd.Instance{
   177  		Id:               input.InstanceId,
   178  		Attributes:       input.Attributes,
   179  		CreatorRequestId: input.CreatorRequestId,
   180  	}
   181  
   182  	return &sd.RegisterInstanceOutput{}, nil
   183  }
   184  
   185  func (s *AWSSDClientStub) UpdateService(input *sd.UpdateServiceInput) (*sd.UpdateServiceOutput, error) {
   186  	out, err := s.GetService(&sd.GetServiceInput{Id: input.Id})
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	origSrv := out.Service
   192  	updateSrv := input.Service
   193  
   194  	origSrv.Description = updateSrv.Description
   195  	origSrv.DnsConfig.DnsRecords = updateSrv.DnsConfig.DnsRecords
   196  
   197  	return &sd.UpdateServiceOutput{}, nil
   198  }
   199  
   200  func (s *AWSSDClientStub) DeleteService(input *sd.DeleteServiceInput) (*sd.DeleteServiceOutput, error) {
   201  	out, err := s.GetService(&sd.GetServiceInput{Id: input.Id})
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	service := out.Service
   207  	namespace := s.services[*service.NamespaceId]
   208  	delete(namespace, *input.Id)
   209  
   210  	return &sd.DeleteServiceOutput{}, nil
   211  }
   212  
   213  func newTestAWSSDProvider(api AWSSDClient, domainFilter endpoint.DomainFilter, namespaceTypeFilter, ownerID string) *AWSSDProvider {
   214  	return &AWSSDProvider{
   215  		client:              api,
   216  		dryRun:              false,
   217  		namespaceFilter:     domainFilter,
   218  		namespaceTypeFilter: newSdNamespaceFilter(namespaceTypeFilter),
   219  		cleanEmptyService:   true,
   220  		ownerID:             ownerID,
   221  	}
   222  }
   223  
   224  // nolint: deadcode
   225  // used for unit test
   226  func instanceToHTTPInstanceSummary(instance *sd.Instance) *sd.HttpInstanceSummary {
   227  	if instance == nil {
   228  		return nil
   229  	}
   230  
   231  	return &sd.HttpInstanceSummary{
   232  		InstanceId: instance.Id,
   233  		Attributes: instance.Attributes,
   234  	}
   235  }
   236  
   237  func TestAWSSDProvider_Records(t *testing.T) {
   238  	namespaces := map[string]*sd.Namespace{
   239  		"private": {
   240  			Id:   aws.String("private"),
   241  			Name: aws.String("private.com"),
   242  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   243  		},
   244  	}
   245  
   246  	services := map[string]map[string]*sd.Service{
   247  		"private": {
   248  			"a-srv": {
   249  				Id:          aws.String("a-srv"),
   250  				Name:        aws.String("service1"),
   251  				Description: aws.String("owner-id"),
   252  				DnsConfig: &sd.DnsConfig{
   253  					NamespaceId:   aws.String("private"),
   254  					RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   255  					DnsRecords: []*sd.DnsRecord{{
   256  						Type: aws.String(sd.RecordTypeA),
   257  						TTL:  aws.Int64(100),
   258  					}},
   259  				},
   260  			},
   261  			"alias-srv": {
   262  				Id:          aws.String("alias-srv"),
   263  				Name:        aws.String("service2"),
   264  				Description: aws.String("owner-id"),
   265  				DnsConfig: &sd.DnsConfig{
   266  					NamespaceId:   aws.String("private"),
   267  					RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   268  					DnsRecords: []*sd.DnsRecord{{
   269  						Type: aws.String(sd.RecordTypeA),
   270  						TTL:  aws.Int64(100),
   271  					}},
   272  				},
   273  			},
   274  			"cname-srv": {
   275  				Id:          aws.String("cname-srv"),
   276  				Name:        aws.String("service3"),
   277  				Description: aws.String("owner-id"),
   278  				DnsConfig: &sd.DnsConfig{
   279  					NamespaceId:   aws.String("private"),
   280  					RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   281  					DnsRecords: []*sd.DnsRecord{{
   282  						Type: aws.String(sd.RecordTypeCname),
   283  						TTL:  aws.Int64(80),
   284  					}},
   285  				},
   286  			},
   287  		},
   288  	}
   289  
   290  	instances := map[string]map[string]*sd.Instance{
   291  		"a-srv": {
   292  			"1.2.3.4": {
   293  				Id: aws.String("1.2.3.4"),
   294  				Attributes: map[string]*string{
   295  					sdInstanceAttrIPV4: aws.String("1.2.3.4"),
   296  				},
   297  			},
   298  			"1.2.3.5": {
   299  				Id: aws.String("1.2.3.5"),
   300  				Attributes: map[string]*string{
   301  					sdInstanceAttrIPV4: aws.String("1.2.3.5"),
   302  				},
   303  			},
   304  		},
   305  		"alias-srv": {
   306  			"load-balancer.us-east-1.elb.amazonaws.com": {
   307  				Id: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
   308  				Attributes: map[string]*string{
   309  					sdInstanceAttrAlias: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
   310  				},
   311  			},
   312  		},
   313  		"cname-srv": {
   314  			"cname.target.com": {
   315  				Id: aws.String("cname.target.com"),
   316  				Attributes: map[string]*string{
   317  					sdInstanceAttrCname: aws.String("cname.target.com"),
   318  				},
   319  			},
   320  		},
   321  	}
   322  
   323  	expectedEndpoints := []*endpoint.Endpoint{
   324  		{DNSName: "service1.private.com", Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5"}, RecordType: endpoint.RecordTypeA, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
   325  		{DNSName: "service2.private.com", Targets: endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
   326  		{DNSName: "service3.private.com", Targets: endpoint.Targets{"cname.target.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 80, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
   327  	}
   328  
   329  	api := &AWSSDClientStub{
   330  		namespaces: namespaces,
   331  		services:   services,
   332  		instances:  instances,
   333  	}
   334  
   335  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   336  
   337  	endpoints, _ := provider.Records(context.Background())
   338  
   339  	assert.True(t, testutils.SameEndpoints(expectedEndpoints, endpoints), "expected and actual endpoints don't match, expected=%v, actual=%v", expectedEndpoints, endpoints)
   340  }
   341  
   342  func TestAWSSDProvider_ApplyChanges(t *testing.T) {
   343  	namespaces := map[string]*sd.Namespace{
   344  		"private": {
   345  			Id:   aws.String("private"),
   346  			Name: aws.String("private.com"),
   347  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   348  		},
   349  	}
   350  
   351  	api := &AWSSDClientStub{
   352  		namespaces: namespaces,
   353  		services:   make(map[string]map[string]*sd.Service),
   354  		instances:  make(map[string]map[string]*sd.Instance),
   355  	}
   356  
   357  	expectedEndpoints := []*endpoint.Endpoint{
   358  		{DNSName: "service1.private.com", Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5"}, RecordType: endpoint.RecordTypeA, RecordTTL: 60},
   359  		{DNSName: "service2.private.com", Targets: endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 80},
   360  		{DNSName: "service3.private.com", Targets: endpoint.Targets{"cname.target.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100},
   361  	}
   362  
   363  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   364  
   365  	ctx := context.Background()
   366  
   367  	// apply creates
   368  	provider.ApplyChanges(ctx, &plan.Changes{
   369  		Create: expectedEndpoints,
   370  	})
   371  
   372  	// make sure services were created
   373  	assert.Len(t, api.services["private"], 3)
   374  	existingServices, _ := provider.ListServicesByNamespaceID(namespaces["private"].Id)
   375  	assert.NotNil(t, existingServices["service1"])
   376  	assert.NotNil(t, existingServices["service2"])
   377  	assert.NotNil(t, existingServices["service3"])
   378  
   379  	// make sure instances were registered
   380  	endpoints, _ := provider.Records(ctx)
   381  	assert.True(t, testutils.SameEndpoints(expectedEndpoints, endpoints), "expected and actual endpoints don't match, expected=%v, actual=%v", expectedEndpoints, endpoints)
   382  
   383  	ctx = context.Background()
   384  	// apply deletes
   385  	provider.ApplyChanges(ctx, &plan.Changes{
   386  		Delete: expectedEndpoints,
   387  	})
   388  
   389  	// make sure all instances are gone
   390  	endpoints, _ = provider.Records(ctx)
   391  	assert.Empty(t, endpoints)
   392  }
   393  
   394  func TestAWSSDProvider_ListNamespaces(t *testing.T) {
   395  	namespaces := map[string]*sd.Namespace{
   396  		"private": {
   397  			Id:   aws.String("private"),
   398  			Name: aws.String("private.com"),
   399  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   400  		},
   401  		"public": {
   402  			Id:   aws.String("public"),
   403  			Name: aws.String("public.com"),
   404  			Type: aws.String(sd.NamespaceTypeDnsPublic),
   405  		},
   406  	}
   407  
   408  	api := &AWSSDClientStub{
   409  		namespaces: namespaces,
   410  	}
   411  
   412  	for _, tc := range []struct {
   413  		msg                 string
   414  		domainFilter        endpoint.DomainFilter
   415  		namespaceTypeFilter string
   416  		expectedNamespaces  []*sd.NamespaceSummary
   417  	}{
   418  		{"public filter", endpoint.NewDomainFilter([]string{}), "public", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
   419  		{"private filter", endpoint.NewDomainFilter([]string{}), "private", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["private"])}},
   420  		{"domain filter", endpoint.NewDomainFilter([]string{"public.com"}), "", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
   421  		{"non-existing domain", endpoint.NewDomainFilter([]string{"xxx.com"}), "", []*sd.NamespaceSummary{}},
   422  	} {
   423  		provider := newTestAWSSDProvider(api, tc.domainFilter, tc.namespaceTypeFilter, "")
   424  
   425  		result, err := provider.ListNamespaces()
   426  		require.NoError(t, err)
   427  
   428  		expectedMap := make(map[string]*sd.NamespaceSummary)
   429  		resultMap := make(map[string]*sd.NamespaceSummary)
   430  		for _, ns := range tc.expectedNamespaces {
   431  			expectedMap[*ns.Id] = ns
   432  		}
   433  		for _, ns := range result {
   434  			resultMap[*ns.Id] = ns
   435  		}
   436  
   437  		if !reflect.DeepEqual(resultMap, expectedMap) {
   438  			t.Errorf("AWSSDProvider.ListNamespaces() error = %v, wantErr %v", result, tc.expectedNamespaces)
   439  		}
   440  	}
   441  }
   442  
   443  func TestAWSSDProvider_ListServicesByNamespace(t *testing.T) {
   444  	namespaces := map[string]*sd.Namespace{
   445  		"private": {
   446  			Id:   aws.String("private"),
   447  			Name: aws.String("private.com"),
   448  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   449  		},
   450  		"public": {
   451  			Id:   aws.String("public"),
   452  			Name: aws.String("public.com"),
   453  			Type: aws.String(sd.NamespaceTypeDnsPublic),
   454  		},
   455  	}
   456  
   457  	services := map[string]map[string]*sd.Service{
   458  		"private": {
   459  			"srv1": {
   460  				Id:          aws.String("srv1"),
   461  				Name:        aws.String("service1"),
   462  				NamespaceId: aws.String("private"),
   463  			},
   464  			"srv2": {
   465  				Id:          aws.String("srv2"),
   466  				Name:        aws.String("service2"),
   467  				NamespaceId: aws.String("private"),
   468  			},
   469  		},
   470  		"public": {
   471  			"srv3": {
   472  				Id:          aws.String("srv3"),
   473  				Name:        aws.String("service3"),
   474  				NamespaceId: aws.String("public"),
   475  			},
   476  		},
   477  	}
   478  
   479  	api := &AWSSDClientStub{
   480  		namespaces: namespaces,
   481  		services:   services,
   482  	}
   483  
   484  	for _, tc := range []struct {
   485  		expectedServices map[string]*sd.Service
   486  	}{
   487  		{map[string]*sd.Service{"service1": services["private"]["srv1"], "service2": services["private"]["srv2"]}},
   488  	} {
   489  		provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   490  
   491  		result, err := provider.ListServicesByNamespaceID(namespaces["private"].Id)
   492  		require.NoError(t, err)
   493  		assert.Equal(t, tc.expectedServices, result)
   494  	}
   495  }
   496  
   497  func TestAWSSDProvider_CreateService(t *testing.T) {
   498  	namespaces := map[string]*sd.Namespace{
   499  		"private": {
   500  			Id:   aws.String("private"),
   501  			Name: aws.String("private.com"),
   502  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   503  		},
   504  	}
   505  
   506  	api := &AWSSDClientStub{
   507  		namespaces: namespaces,
   508  		services:   make(map[string]map[string]*sd.Service),
   509  	}
   510  
   511  	expectedServices := make(map[string]*sd.Service)
   512  
   513  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   514  
   515  	// A type
   516  	provider.CreateService(aws.String("private"), aws.String("A-srv"), &endpoint.Endpoint{
   517  		RecordType: endpoint.RecordTypeA,
   518  		RecordTTL:  60,
   519  		Targets:    endpoint.Targets{"1.2.3.4"},
   520  	})
   521  	expectedServices["A-srv"] = &sd.Service{
   522  		Name: aws.String("A-srv"),
   523  		DnsConfig: &sd.DnsConfig{
   524  			RoutingPolicy: aws.String(sd.RoutingPolicyMultivalue),
   525  			DnsRecords: []*sd.DnsRecord{{
   526  				Type: aws.String(sd.RecordTypeA),
   527  				TTL:  aws.Int64(60),
   528  			}},
   529  		},
   530  		NamespaceId: aws.String("private"),
   531  	}
   532  
   533  	// CNAME type
   534  	provider.CreateService(aws.String("private"), aws.String("CNAME-srv"), &endpoint.Endpoint{
   535  		RecordType: endpoint.RecordTypeCNAME,
   536  		RecordTTL:  80,
   537  		Targets:    endpoint.Targets{"cname.target.com"},
   538  	})
   539  	expectedServices["CNAME-srv"] = &sd.Service{
   540  		Name: aws.String("CNAME-srv"),
   541  		DnsConfig: &sd.DnsConfig{
   542  			RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   543  			DnsRecords: []*sd.DnsRecord{{
   544  				Type: aws.String(sd.RecordTypeCname),
   545  				TTL:  aws.Int64(80),
   546  			}},
   547  		},
   548  		NamespaceId: aws.String("private"),
   549  	}
   550  
   551  	// ALIAS type
   552  	provider.CreateService(aws.String("private"), aws.String("ALIAS-srv"), &endpoint.Endpoint{
   553  		RecordType: endpoint.RecordTypeCNAME,
   554  		RecordTTL:  100,
   555  		Targets:    endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com"},
   556  	})
   557  	expectedServices["ALIAS-srv"] = &sd.Service{
   558  		Name: aws.String("ALIAS-srv"),
   559  		DnsConfig: &sd.DnsConfig{
   560  			RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   561  			DnsRecords: []*sd.DnsRecord{{
   562  				Type: aws.String(sd.RecordTypeA),
   563  				TTL:  aws.Int64(100),
   564  			}},
   565  		},
   566  		NamespaceId: aws.String("private"),
   567  	}
   568  
   569  	validateAWSSDServicesMapsEqual(t, expectedServices, api.services["private"])
   570  }
   571  
   572  func validateAWSSDServicesMapsEqual(t *testing.T, expected map[string]*sd.Service, services map[string]*sd.Service) {
   573  	require.Len(t, services, len(expected))
   574  
   575  	for _, srv := range services {
   576  		validateAWSSDServicesEqual(t, expected[*srv.Name], srv)
   577  	}
   578  }
   579  
   580  func validateAWSSDServicesEqual(t *testing.T, expected *sd.Service, srv *sd.Service) {
   581  	assert.Equal(t, aws.StringValue(expected.Description), aws.StringValue(srv.Description))
   582  	assert.Equal(t, aws.StringValue(expected.Name), aws.StringValue(srv.Name))
   583  	assert.True(t, reflect.DeepEqual(*expected.DnsConfig, *srv.DnsConfig))
   584  }
   585  
   586  func TestAWSSDProvider_UpdateService(t *testing.T) {
   587  	namespaces := map[string]*sd.Namespace{
   588  		"private": {
   589  			Id:   aws.String("private"),
   590  			Name: aws.String("private.com"),
   591  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   592  		},
   593  	}
   594  
   595  	services := map[string]map[string]*sd.Service{
   596  		"private": {
   597  			"srv1": {
   598  				Id:   aws.String("srv1"),
   599  				Name: aws.String("service1"),
   600  				DnsConfig: &sd.DnsConfig{
   601  					NamespaceId:   aws.String("private"),
   602  					RoutingPolicy: aws.String(sd.RoutingPolicyMultivalue),
   603  					DnsRecords: []*sd.DnsRecord{{
   604  						Type: aws.String(sd.RecordTypeA),
   605  						TTL:  aws.Int64(60),
   606  					}},
   607  				},
   608  			},
   609  		},
   610  	}
   611  
   612  	api := &AWSSDClientStub{
   613  		namespaces: namespaces,
   614  		services:   services,
   615  	}
   616  
   617  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   618  
   619  	// update service with different TTL
   620  	provider.UpdateService(services["private"]["srv1"], &endpoint.Endpoint{
   621  		RecordType: endpoint.RecordTypeA,
   622  		RecordTTL:  100,
   623  	})
   624  
   625  	assert.Equal(t, int64(100), *api.services["private"]["srv1"].DnsConfig.DnsRecords[0].TTL)
   626  }
   627  
   628  func TestAWSSDProvider_DeleteService(t *testing.T) {
   629  	namespaces := map[string]*sd.Namespace{
   630  		"private": {
   631  			Id:   aws.String("private"),
   632  			Name: aws.String("private.com"),
   633  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   634  		},
   635  	}
   636  
   637  	services := map[string]map[string]*sd.Service{
   638  		"private": {
   639  			"srv1": {
   640  				Id:          aws.String("srv1"),
   641  				Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
   642  				Name:        aws.String("service1"),
   643  				NamespaceId: aws.String("private"),
   644  			},
   645  			"srv2": {
   646  				Id:          aws.String("srv2"),
   647  				Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
   648  				Name:        aws.String("service2"),
   649  				NamespaceId: aws.String("private"),
   650  			},
   651  			"srv3": {
   652  				Id:          aws.String("srv3"),
   653  				Description: aws.String("heritage=external-dns,external-dns/owner=owner-id,external-dns/resource=virtualservice/grpc-server/validate-grpc-server"),
   654  				Name:        aws.String("service3"),
   655  				NamespaceId: aws.String("private"),
   656  			},
   657  		},
   658  	}
   659  
   660  	api := &AWSSDClientStub{
   661  		namespaces: namespaces,
   662  		services:   services,
   663  	}
   664  
   665  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "owner-id")
   666  
   667  	// delete first service
   668  	err := provider.DeleteService(services["private"]["srv1"])
   669  	assert.NoError(t, err)
   670  	assert.Len(t, api.services["private"], 2)
   671  
   672  	// delete third service
   673  	err1 := provider.DeleteService(services["private"]["srv3"])
   674  	assert.NoError(t, err1)
   675  	assert.Len(t, api.services["private"], 1)
   676  
   677  	expectedServices := map[string]*sd.Service{
   678  		"srv2": {
   679  			Id:          aws.String("srv2"),
   680  			Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
   681  			Name:        aws.String("service2"),
   682  			NamespaceId: aws.String("private"),
   683  		},
   684  	}
   685  
   686  	assert.Equal(t, expectedServices, api.services["private"])
   687  }
   688  
   689  func TestAWSSDProvider_RegisterInstance(t *testing.T) {
   690  	namespaces := map[string]*sd.Namespace{
   691  		"private": {
   692  			Id:   aws.String("private"),
   693  			Name: aws.String("private.com"),
   694  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   695  		},
   696  	}
   697  
   698  	services := map[string]map[string]*sd.Service{
   699  		"private": {
   700  			"a-srv": {
   701  				Id:   aws.String("a-srv"),
   702  				Name: aws.String("service1"),
   703  				DnsConfig: &sd.DnsConfig{
   704  					NamespaceId:   aws.String("private"),
   705  					RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   706  					DnsRecords: []*sd.DnsRecord{{
   707  						Type: aws.String(sd.RecordTypeA),
   708  						TTL:  aws.Int64(60),
   709  					}},
   710  				},
   711  			},
   712  			"cname-srv": {
   713  				Id:   aws.String("cname-srv"),
   714  				Name: aws.String("service2"),
   715  				DnsConfig: &sd.DnsConfig{
   716  					NamespaceId:   aws.String("private"),
   717  					RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   718  					DnsRecords: []*sd.DnsRecord{{
   719  						Type: aws.String(sd.RecordTypeCname),
   720  						TTL:  aws.Int64(60),
   721  					}},
   722  				},
   723  			},
   724  			"alias-srv": {
   725  				Id:   aws.String("alias-srv"),
   726  				Name: aws.String("service3"),
   727  				DnsConfig: &sd.DnsConfig{
   728  					NamespaceId:   aws.String("private"),
   729  					RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
   730  					DnsRecords: []*sd.DnsRecord{{
   731  						Type: aws.String(sd.RecordTypeA),
   732  						TTL:  aws.Int64(60),
   733  					}},
   734  				},
   735  			},
   736  		},
   737  	}
   738  
   739  	api := &AWSSDClientStub{
   740  		namespaces: namespaces,
   741  		services:   services,
   742  		instances:  make(map[string]map[string]*sd.Instance),
   743  	}
   744  
   745  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   746  
   747  	expectedInstances := make(map[string]*sd.Instance)
   748  
   749  	// IP-based instance
   750  	provider.RegisterInstance(services["private"]["a-srv"], &endpoint.Endpoint{
   751  		RecordType: endpoint.RecordTypeA,
   752  		DNSName:    "service1.private.com.",
   753  		RecordTTL:  300,
   754  		Targets:    endpoint.Targets{"1.2.3.4", "1.2.3.5"},
   755  	})
   756  	expectedInstances["1.2.3.4"] = &sd.Instance{
   757  		Id: aws.String("1.2.3.4"),
   758  		Attributes: map[string]*string{
   759  			sdInstanceAttrIPV4: aws.String("1.2.3.4"),
   760  		},
   761  	}
   762  	expectedInstances["1.2.3.5"] = &sd.Instance{
   763  		Id: aws.String("1.2.3.5"),
   764  		Attributes: map[string]*string{
   765  			sdInstanceAttrIPV4: aws.String("1.2.3.5"),
   766  		},
   767  	}
   768  
   769  	// AWS ELB instance (ALIAS)
   770  	provider.RegisterInstance(services["private"]["alias-srv"], &endpoint.Endpoint{
   771  		RecordType: endpoint.RecordTypeCNAME,
   772  		DNSName:    "service1.private.com.",
   773  		RecordTTL:  300,
   774  		Targets:    endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com", "load-balancer.us-west-2.elb.amazonaws.com"},
   775  	})
   776  	expectedInstances["load-balancer.us-east-1.elb.amazonaws.com"] = &sd.Instance{
   777  		Id: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
   778  		Attributes: map[string]*string{
   779  			sdInstanceAttrAlias: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
   780  		},
   781  	}
   782  	expectedInstances["load-balancer.us-west-2.elb.amazonaws.com"] = &sd.Instance{
   783  		Id: aws.String("load-balancer.us-west-2.elb.amazonaws.com"),
   784  		Attributes: map[string]*string{
   785  			sdInstanceAttrAlias: aws.String("load-balancer.us-west-2.elb.amazonaws.com"),
   786  		},
   787  	}
   788  
   789  	// AWS NLB instance (ALIAS)
   790  	provider.RegisterInstance(services["private"]["alias-srv"], &endpoint.Endpoint{
   791  		RecordType: endpoint.RecordTypeCNAME,
   792  		DNSName:    "service1.private.com.",
   793  		RecordTTL:  300,
   794  		Targets:    endpoint.Targets{"load-balancer.elb.us-west-2.amazonaws.com"},
   795  	})
   796  	expectedInstances["load-balancer.elb.us-west-2.amazonaws.com"] = &sd.Instance{
   797  		Id: aws.String("load-balancer.elb.us-west-2.amazonaws.com"),
   798  		Attributes: map[string]*string{
   799  			sdInstanceAttrAlias: aws.String("load-balancer.elb.us-west-2.amazonaws.com"),
   800  		},
   801  	}
   802  
   803  	// CNAME instance
   804  	provider.RegisterInstance(services["private"]["cname-srv"], &endpoint.Endpoint{
   805  		RecordType: endpoint.RecordTypeCNAME,
   806  		DNSName:    "service2.private.com.",
   807  		RecordTTL:  300,
   808  		Targets:    endpoint.Targets{"cname.target.com"},
   809  	})
   810  	expectedInstances["cname.target.com"] = &sd.Instance{
   811  		Id: aws.String("cname.target.com"),
   812  		Attributes: map[string]*string{
   813  			sdInstanceAttrCname: aws.String("cname.target.com"),
   814  		},
   815  	}
   816  
   817  	// validate instances
   818  	for _, srvInst := range api.instances {
   819  		for id, inst := range srvInst {
   820  			if !reflect.DeepEqual(*expectedInstances[id], *inst) {
   821  				t.Errorf("Instances don't match, expected = %v, actual %v", *expectedInstances[id], *inst)
   822  			}
   823  		}
   824  	}
   825  }
   826  
   827  func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
   828  	namespaces := map[string]*sd.Namespace{
   829  		"private": {
   830  			Id:   aws.String("private"),
   831  			Name: aws.String("private.com"),
   832  			Type: aws.String(sd.NamespaceTypeDnsPrivate),
   833  		},
   834  	}
   835  
   836  	services := map[string]map[string]*sd.Service{
   837  		"private": {
   838  			"srv1": {
   839  				Id:   aws.String("srv1"),
   840  				Name: aws.String("service1"),
   841  			},
   842  		},
   843  	}
   844  
   845  	instances := map[string]map[string]*sd.Instance{
   846  		"srv1": {
   847  			"1.2.3.4": {
   848  				Id: aws.String("1.2.3.4"),
   849  				Attributes: map[string]*string{
   850  					sdInstanceAttrIPV4: aws.String("1.2.3.4"),
   851  				},
   852  			},
   853  		},
   854  	}
   855  
   856  	api := &AWSSDClientStub{
   857  		namespaces: namespaces,
   858  		services:   services,
   859  		instances:  instances,
   860  	}
   861  
   862  	provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
   863  
   864  	provider.DeregisterInstance(services["private"]["srv1"], endpoint.NewEndpoint("srv1.private.com.", endpoint.RecordTypeA, "1.2.3.4"))
   865  
   866  	assert.Len(t, instances["srv1"], 0)
   867  }