From e9c129a9e9e8f1c08056a323d821478863d3a73d Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Fri, 10 Nov 2023 13:48:03 -0800 Subject: [PATCH 1/3] Optimize target group operations --- controllers/gateway_controller.go | 6 +- controllers/route_controller_test.go | 3 + go.mod | 1 + go.sum | 2 + pkg/aws/cloud.go | 18 +- pkg/aws/cloud_mocks.go | 14 + pkg/aws/services/tagging.go | 97 +++ pkg/aws/services/tagging_mocks.go | 599 ++++++++++++++++++ pkg/aws/services/vpclattice.go | 44 +- pkg/deploy/lattice/rule_synthesizer.go | 10 +- pkg/deploy/lattice/rule_synthesizer_test.go | 16 +- pkg/deploy/lattice/target_group_manager.go | 169 +++-- .../lattice/target_group_manager_test.go | 298 ++++----- .../lattice/target_group_synthesizer.go | 77 +-- .../lattice/target_group_synthesizer_test.go | 154 +++-- pkg/gateway/model_build_targetgroup.go | 2 + pkg/model/lattice/targetgroup.go | 27 +- 17 files changed, 1089 insertions(+), 448 deletions(-) create mode 100644 pkg/aws/services/tagging.go create mode 100644 pkg/aws/services/tagging_mocks.go diff --git a/controllers/gateway_controller.go b/controllers/gateway_controller.go index 94723258..b90e590d 100644 --- a/controllers/gateway_controller.go +++ b/controllers/gateway_controller.go @@ -298,8 +298,9 @@ func UpdateGWListenerStatus(ctx context.Context, k8sClient client.Client, gw *gw return err } - // Add one of lattice domains as GW address. This can represent incorrect value in some cases (e.g. cross-account) - // TODO: support multiple endpoint addresses across services. + // Add one of lattice domains as GW address. This is supposed to be a single ingress endpoint (or a single pool of them) + // but we have different endpoints for each service. This can represent incorrect value in some cases (e.g. cross-account) + // Due to size limit, we cannot put all service addresses here. if len(routes) > 0 { gw.Status.Addresses = []gwv1beta1.GatewayAddress{} addressType := gwv1beta1.HostnameAddressType @@ -310,6 +311,7 @@ func UpdateGWListenerStatus(ctx context.Context, k8sClient client.Client, gw *gw Type: &addressType, Value: domain, }) + break } } } diff --git a/controllers/route_controller_test.go b/controllers/route_controller_test.go index cd55e350..17513312 100644 --- a/controllers/route_controller_test.go +++ b/controllers/route_controller_test.go @@ -157,7 +157,9 @@ func TestRouteReconciler_ReconcileCreates(t *testing.T) { mockCloud := aws2.NewMockCloud(c) mockLattice := mocks.NewMockLattice(c) + mockTagging := mocks.NewMockTagging(c) mockCloud.EXPECT().Lattice().Return(mockLattice).AnyTimes() + mockCloud.EXPECT().Tagging().Return(mockTagging).AnyTimes() mockCloud.EXPECT().Config().Return( aws2.CloudConfig{ VpcId: config.VpcID, @@ -204,6 +206,7 @@ func TestRouteReconciler_ReconcileCreates(t *testing.T) { }, }, nil) // will trigger DNS Update + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return( []*vpclattice.TargetGroupSummary{}, nil).AnyTimes() // this will cause us to skip "unused delete" step mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return( diff --git a/go.mod b/go.mod index 609fa4aa..4647b54a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/aws/aws-sdk-go v1.44.321 github.com/go-logr/zapr v1.2.3 github.com/golang/mock v1.6.0 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.24.1 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 8b57af69..45e07013 100644 --- a/go.sum +++ b/go.sum @@ -172,6 +172,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= diff --git a/pkg/aws/cloud.go b/pkg/aws/cloud.go index 4c0abc8e..34efe234 100644 --- a/pkg/aws/cloud.go +++ b/pkg/aws/cloud.go @@ -30,6 +30,7 @@ type CloudConfig struct { type Cloud interface { Config() CloudConfig Lattice() services.Lattice + Tagging() services.Tagging // creates lattice tags with default values populated DefaultTags() services.Tags @@ -70,7 +71,8 @@ func NewCloud(log gwlog.Logger, cfg CloudConfig) (Cloud, error) { }) lattice := services.NewDefaultLattice(sess, cfg.Region) - cl := NewDefaultCloud(lattice, cfg) + tagging := services.NewDefaultTagging(sess, cfg.Region) + cl := NewDefaultCloudWithTagging(lattice, tagging, cfg) return cl, nil } @@ -83,9 +85,19 @@ func NewDefaultCloud(lattice services.Lattice, cfg CloudConfig) Cloud { } } +func NewDefaultCloudWithTagging(lattice services.Lattice, tagging services.Tagging, cfg CloudConfig) Cloud { + return &defaultCloud{ + cfg: cfg, + lattice: lattice, + tagging: tagging, + managedByTag: getManagedByTag(cfg), + } +} + type defaultCloud struct { cfg CloudConfig lattice services.Lattice + tagging services.Tagging managedByTag string } @@ -93,6 +105,10 @@ func (c *defaultCloud) Lattice() services.Lattice { return c.lattice } +func (c *defaultCloud) Tagging() services.Tagging { + return c.tagging +} + func (c *defaultCloud) Config() CloudConfig { return c.cfg } diff --git a/pkg/aws/cloud_mocks.go b/pkg/aws/cloud_mocks.go index d9d57e62..bd8fe67b 100644 --- a/pkg/aws/cloud_mocks.go +++ b/pkg/aws/cloud_mocks.go @@ -106,6 +106,20 @@ func (mr *MockCloudMockRecorder) Lattice() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lattice", reflect.TypeOf((*MockCloud)(nil).Lattice)) } +// Tagging mocks base method. +func (m *MockCloud) Tagging() services.Tagging { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Tagging") + ret0, _ := ret[0].(services.Tagging) + return ret0 +} + +// Tagging indicates an expected call of Tagging. +func (mr *MockCloudMockRecorder) Tagging() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tagging", reflect.TypeOf((*MockCloud)(nil).Tagging)) +} + // TryOwn mocks base method. func (m *MockCloud) TryOwn(arg0 context.Context, arg1 string) (bool, error) { m.ctrl.T.Helper() diff --git a/pkg/aws/services/tagging.go b/pkg/aws/services/tagging.go new file mode 100644 index 00000000..52f0db63 --- /dev/null +++ b/pkg/aws/services/tagging.go @@ -0,0 +1,97 @@ +package services + +import ( + "context" + "github.com/aws/aws-application-networking-k8s/pkg/utils" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + taggingapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" + taggingapiiface "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface" +) + +//go:generate mockgen -destination tagging_mocks.go -package services github.com/aws/aws-application-networking-k8s/pkg/aws/services Tagging + +const ( + resourceTypePrefix = "vpc-lattice:" + + ResourceTypeTargetGroup = resourceTypePrefix + "targetgroup" + + maxArnsPerGetResourcesApi = 100 +) + +type Tags = map[string]*string + +type Tagging interface { + taggingapiiface.ResourceGroupsTaggingAPIAPI + + // Receives a list of arns and returns arn-to-tags map. + GetTagsFromArns(ctx context.Context, arns []*string) (map[string]Tags, error) + + // Finds one resource that matches the given set of tags. + FindResourceWithTags(ctx context.Context, resourceType string, tags Tags) (*string, error) +} + +type defaultTagging struct { + taggingapiiface.ResourceGroupsTaggingAPIAPI +} + +func (t *defaultTagging) GetTagsFromArns(ctx context.Context, arns []*string) (map[string]Tags, error) { + chunks := utils.Chunks(arns, maxArnsPerGetResourcesApi) + result := make(map[string]Tags) + + for _, chunk := range chunks { + input := &taggingapi.GetResourcesInput{ + ResourceARNList: chunk, + } + err := t.GetResourcesPagesWithContext(ctx, input, func(page *taggingapi.GetResourcesOutput, lastPage bool) bool { + for _, r := range page.ResourceTagMappingList { + result[*r.ResourceARN] = convertTags(r.Tags) + } + return true + }) + if err != nil { + return nil, err + } + } + return result, nil +} + +func (t *defaultTagging) FindResourceWithTags(ctx context.Context, resourceType string, tags Tags) (*string, error) { + input := &taggingapi.GetResourcesInput{ + TagFilters: convertTagsToFilter(tags), + ResourceTypeFilters: []*string{aws.String(resourceType)}, + } + resp, err := t.GetResourcesWithContext(ctx, input) + if err != nil { + return nil, err + } + if len(resp.ResourceTagMappingList) == 0 { + return nil, NewNotFoundError("tag", "matching criteria") + } + // assume one result + return resp.ResourceTagMappingList[0].ResourceARN, nil +} + +func NewDefaultTagging(sess *session.Session, region string) *defaultTagging { + api := taggingapi.New(sess, &aws.Config{Region: aws.String(region)}) + return &defaultTagging{ResourceGroupsTaggingAPIAPI: api} +} + +func convertTags(tags []*taggingapi.Tag) Tags { + out := make(Tags) + for _, tag := range tags { + out[*tag.Key] = tag.Value + } + return out +} + +func convertTagsToFilter(tags Tags) []*taggingapi.TagFilter { + filters := make([]*taggingapi.TagFilter, 0, len(tags)) + for k, v := range tags { + filters = append(filters, &taggingapi.TagFilter{ + Key: aws.String(k), + Values: []*string{v}, + }) + } + return filters +} diff --git a/pkg/aws/services/tagging_mocks.go b/pkg/aws/services/tagging_mocks.go new file mode 100644 index 00000000..62a2672d --- /dev/null +++ b/pkg/aws/services/tagging_mocks.go @@ -0,0 +1,599 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/aws-application-networking-k8s/pkg/aws/services (interfaces: Tagging) + +// Package services is a generated GoMock package. +package services + +import ( + context "context" + reflect "reflect" + + request "github.com/aws/aws-sdk-go/aws/request" + resourcegroupstaggingapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" + gomock "github.com/golang/mock/gomock" +) + +// MockTagging is a mock of Tagging interface. +type MockTagging struct { + ctrl *gomock.Controller + recorder *MockTaggingMockRecorder +} + +// MockTaggingMockRecorder is the mock recorder for MockTagging. +type MockTaggingMockRecorder struct { + mock *MockTagging +} + +// NewMockTagging creates a new mock instance. +func NewMockTagging(ctrl *gomock.Controller) *MockTagging { + mock := &MockTagging{ctrl: ctrl} + mock.recorder = &MockTaggingMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTagging) EXPECT() *MockTaggingMockRecorder { + return m.recorder +} + +// DescribeReportCreation mocks base method. +func (m *MockTagging) DescribeReportCreation(arg0 *resourcegroupstaggingapi.DescribeReportCreationInput) (*resourcegroupstaggingapi.DescribeReportCreationOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeReportCreation", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.DescribeReportCreationOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DescribeReportCreation indicates an expected call of DescribeReportCreation. +func (mr *MockTaggingMockRecorder) DescribeReportCreation(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeReportCreation", reflect.TypeOf((*MockTagging)(nil).DescribeReportCreation), arg0) +} + +// DescribeReportCreationRequest mocks base method. +func (m *MockTagging) DescribeReportCreationRequest(arg0 *resourcegroupstaggingapi.DescribeReportCreationInput) (*request.Request, *resourcegroupstaggingapi.DescribeReportCreationOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeReportCreationRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.DescribeReportCreationOutput) + return ret0, ret1 +} + +// DescribeReportCreationRequest indicates an expected call of DescribeReportCreationRequest. +func (mr *MockTaggingMockRecorder) DescribeReportCreationRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeReportCreationRequest", reflect.TypeOf((*MockTagging)(nil).DescribeReportCreationRequest), arg0) +} + +// DescribeReportCreationWithContext mocks base method. +func (m *MockTagging) DescribeReportCreationWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.DescribeReportCreationInput, arg2 ...request.Option) (*resourcegroupstaggingapi.DescribeReportCreationOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DescribeReportCreationWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.DescribeReportCreationOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DescribeReportCreationWithContext indicates an expected call of DescribeReportCreationWithContext. +func (mr *MockTaggingMockRecorder) DescribeReportCreationWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeReportCreationWithContext", reflect.TypeOf((*MockTagging)(nil).DescribeReportCreationWithContext), varargs...) +} + +// FindResourceWithTags mocks base method. +func (m *MockTagging) FindResourceWithTags(arg0 context.Context, arg1 string, arg2 map[string]*string) (*string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindResourceWithTags", arg0, arg1, arg2) + ret0, _ := ret[0].(*string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindResourceWithTags indicates an expected call of FindResourceWithTags. +func (mr *MockTaggingMockRecorder) FindResourceWithTags(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindResourceWithTags", reflect.TypeOf((*MockTagging)(nil).FindResourceWithTags), arg0, arg1, arg2) +} + +// GetComplianceSummary mocks base method. +func (m *MockTagging) GetComplianceSummary(arg0 *resourcegroupstaggingapi.GetComplianceSummaryInput) (*resourcegroupstaggingapi.GetComplianceSummaryOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetComplianceSummary", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetComplianceSummaryOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetComplianceSummary indicates an expected call of GetComplianceSummary. +func (mr *MockTaggingMockRecorder) GetComplianceSummary(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComplianceSummary", reflect.TypeOf((*MockTagging)(nil).GetComplianceSummary), arg0) +} + +// GetComplianceSummaryPages mocks base method. +func (m *MockTagging) GetComplianceSummaryPages(arg0 *resourcegroupstaggingapi.GetComplianceSummaryInput, arg1 func(*resourcegroupstaggingapi.GetComplianceSummaryOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetComplianceSummaryPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetComplianceSummaryPages indicates an expected call of GetComplianceSummaryPages. +func (mr *MockTaggingMockRecorder) GetComplianceSummaryPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComplianceSummaryPages", reflect.TypeOf((*MockTagging)(nil).GetComplianceSummaryPages), arg0, arg1) +} + +// GetComplianceSummaryPagesWithContext mocks base method. +func (m *MockTagging) GetComplianceSummaryPagesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetComplianceSummaryInput, arg2 func(*resourcegroupstaggingapi.GetComplianceSummaryOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetComplianceSummaryPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetComplianceSummaryPagesWithContext indicates an expected call of GetComplianceSummaryPagesWithContext. +func (mr *MockTaggingMockRecorder) GetComplianceSummaryPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComplianceSummaryPagesWithContext", reflect.TypeOf((*MockTagging)(nil).GetComplianceSummaryPagesWithContext), varargs...) +} + +// GetComplianceSummaryRequest mocks base method. +func (m *MockTagging) GetComplianceSummaryRequest(arg0 *resourcegroupstaggingapi.GetComplianceSummaryInput) (*request.Request, *resourcegroupstaggingapi.GetComplianceSummaryOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetComplianceSummaryRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.GetComplianceSummaryOutput) + return ret0, ret1 +} + +// GetComplianceSummaryRequest indicates an expected call of GetComplianceSummaryRequest. +func (mr *MockTaggingMockRecorder) GetComplianceSummaryRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComplianceSummaryRequest", reflect.TypeOf((*MockTagging)(nil).GetComplianceSummaryRequest), arg0) +} + +// GetComplianceSummaryWithContext mocks base method. +func (m *MockTagging) GetComplianceSummaryWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetComplianceSummaryInput, arg2 ...request.Option) (*resourcegroupstaggingapi.GetComplianceSummaryOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetComplianceSummaryWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetComplianceSummaryOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetComplianceSummaryWithContext indicates an expected call of GetComplianceSummaryWithContext. +func (mr *MockTaggingMockRecorder) GetComplianceSummaryWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComplianceSummaryWithContext", reflect.TypeOf((*MockTagging)(nil).GetComplianceSummaryWithContext), varargs...) +} + +// GetResources mocks base method. +func (m *MockTagging) GetResources(arg0 *resourcegroupstaggingapi.GetResourcesInput) (*resourcegroupstaggingapi.GetResourcesOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetResources", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetResourcesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetResources indicates an expected call of GetResources. +func (mr *MockTaggingMockRecorder) GetResources(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResources", reflect.TypeOf((*MockTagging)(nil).GetResources), arg0) +} + +// GetResourcesPages mocks base method. +func (m *MockTagging) GetResourcesPages(arg0 *resourcegroupstaggingapi.GetResourcesInput, arg1 func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetResourcesPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetResourcesPages indicates an expected call of GetResourcesPages. +func (mr *MockTaggingMockRecorder) GetResourcesPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourcesPages", reflect.TypeOf((*MockTagging)(nil).GetResourcesPages), arg0, arg1) +} + +// GetResourcesPagesWithContext mocks base method. +func (m *MockTagging) GetResourcesPagesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetResourcesInput, arg2 func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetResourcesPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetResourcesPagesWithContext indicates an expected call of GetResourcesPagesWithContext. +func (mr *MockTaggingMockRecorder) GetResourcesPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourcesPagesWithContext", reflect.TypeOf((*MockTagging)(nil).GetResourcesPagesWithContext), varargs...) +} + +// GetResourcesRequest mocks base method. +func (m *MockTagging) GetResourcesRequest(arg0 *resourcegroupstaggingapi.GetResourcesInput) (*request.Request, *resourcegroupstaggingapi.GetResourcesOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetResourcesRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.GetResourcesOutput) + return ret0, ret1 +} + +// GetResourcesRequest indicates an expected call of GetResourcesRequest. +func (mr *MockTaggingMockRecorder) GetResourcesRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourcesRequest", reflect.TypeOf((*MockTagging)(nil).GetResourcesRequest), arg0) +} + +// GetResourcesWithContext mocks base method. +func (m *MockTagging) GetResourcesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetResourcesInput, arg2 ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetResourcesWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetResourcesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetResourcesWithContext indicates an expected call of GetResourcesWithContext. +func (mr *MockTaggingMockRecorder) GetResourcesWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourcesWithContext", reflect.TypeOf((*MockTagging)(nil).GetResourcesWithContext), varargs...) +} + +// GetTagKeys mocks base method. +func (m *MockTagging) GetTagKeys(arg0 *resourcegroupstaggingapi.GetTagKeysInput) (*resourcegroupstaggingapi.GetTagKeysOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagKeys", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetTagKeysOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTagKeys indicates an expected call of GetTagKeys. +func (mr *MockTaggingMockRecorder) GetTagKeys(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagKeys", reflect.TypeOf((*MockTagging)(nil).GetTagKeys), arg0) +} + +// GetTagKeysPages mocks base method. +func (m *MockTagging) GetTagKeysPages(arg0 *resourcegroupstaggingapi.GetTagKeysInput, arg1 func(*resourcegroupstaggingapi.GetTagKeysOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagKeysPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetTagKeysPages indicates an expected call of GetTagKeysPages. +func (mr *MockTaggingMockRecorder) GetTagKeysPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagKeysPages", reflect.TypeOf((*MockTagging)(nil).GetTagKeysPages), arg0, arg1) +} + +// GetTagKeysPagesWithContext mocks base method. +func (m *MockTagging) GetTagKeysPagesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetTagKeysInput, arg2 func(*resourcegroupstaggingapi.GetTagKeysOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTagKeysPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetTagKeysPagesWithContext indicates an expected call of GetTagKeysPagesWithContext. +func (mr *MockTaggingMockRecorder) GetTagKeysPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagKeysPagesWithContext", reflect.TypeOf((*MockTagging)(nil).GetTagKeysPagesWithContext), varargs...) +} + +// GetTagKeysRequest mocks base method. +func (m *MockTagging) GetTagKeysRequest(arg0 *resourcegroupstaggingapi.GetTagKeysInput) (*request.Request, *resourcegroupstaggingapi.GetTagKeysOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagKeysRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.GetTagKeysOutput) + return ret0, ret1 +} + +// GetTagKeysRequest indicates an expected call of GetTagKeysRequest. +func (mr *MockTaggingMockRecorder) GetTagKeysRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagKeysRequest", reflect.TypeOf((*MockTagging)(nil).GetTagKeysRequest), arg0) +} + +// GetTagKeysWithContext mocks base method. +func (m *MockTagging) GetTagKeysWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetTagKeysInput, arg2 ...request.Option) (*resourcegroupstaggingapi.GetTagKeysOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTagKeysWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetTagKeysOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTagKeysWithContext indicates an expected call of GetTagKeysWithContext. +func (mr *MockTaggingMockRecorder) GetTagKeysWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagKeysWithContext", reflect.TypeOf((*MockTagging)(nil).GetTagKeysWithContext), varargs...) +} + +// GetTagValues mocks base method. +func (m *MockTagging) GetTagValues(arg0 *resourcegroupstaggingapi.GetTagValuesInput) (*resourcegroupstaggingapi.GetTagValuesOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagValues", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetTagValuesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTagValues indicates an expected call of GetTagValues. +func (mr *MockTaggingMockRecorder) GetTagValues(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagValues", reflect.TypeOf((*MockTagging)(nil).GetTagValues), arg0) +} + +// GetTagValuesPages mocks base method. +func (m *MockTagging) GetTagValuesPages(arg0 *resourcegroupstaggingapi.GetTagValuesInput, arg1 func(*resourcegroupstaggingapi.GetTagValuesOutput, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagValuesPages", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetTagValuesPages indicates an expected call of GetTagValuesPages. +func (mr *MockTaggingMockRecorder) GetTagValuesPages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagValuesPages", reflect.TypeOf((*MockTagging)(nil).GetTagValuesPages), arg0, arg1) +} + +// GetTagValuesPagesWithContext mocks base method. +func (m *MockTagging) GetTagValuesPagesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetTagValuesInput, arg2 func(*resourcegroupstaggingapi.GetTagValuesOutput, bool) bool, arg3 ...request.Option) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTagValuesPagesWithContext", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetTagValuesPagesWithContext indicates an expected call of GetTagValuesPagesWithContext. +func (mr *MockTaggingMockRecorder) GetTagValuesPagesWithContext(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagValuesPagesWithContext", reflect.TypeOf((*MockTagging)(nil).GetTagValuesPagesWithContext), varargs...) +} + +// GetTagValuesRequest mocks base method. +func (m *MockTagging) GetTagValuesRequest(arg0 *resourcegroupstaggingapi.GetTagValuesInput) (*request.Request, *resourcegroupstaggingapi.GetTagValuesOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagValuesRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.GetTagValuesOutput) + return ret0, ret1 +} + +// GetTagValuesRequest indicates an expected call of GetTagValuesRequest. +func (mr *MockTaggingMockRecorder) GetTagValuesRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagValuesRequest", reflect.TypeOf((*MockTagging)(nil).GetTagValuesRequest), arg0) +} + +// GetTagValuesWithContext mocks base method. +func (m *MockTagging) GetTagValuesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.GetTagValuesInput, arg2 ...request.Option) (*resourcegroupstaggingapi.GetTagValuesOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTagValuesWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.GetTagValuesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTagValuesWithContext indicates an expected call of GetTagValuesWithContext. +func (mr *MockTaggingMockRecorder) GetTagValuesWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagValuesWithContext", reflect.TypeOf((*MockTagging)(nil).GetTagValuesWithContext), varargs...) +} + +// GetTagsFromArns mocks base method. +func (m *MockTagging) GetTagsFromArns(arg0 context.Context, arg1 []*string) (map[string]map[string]*string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTagsFromArns", arg0, arg1) + ret0, _ := ret[0].(map[string]map[string]*string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTagsFromArns indicates an expected call of GetTagsFromArns. +func (mr *MockTaggingMockRecorder) GetTagsFromArns(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagsFromArns", reflect.TypeOf((*MockTagging)(nil).GetTagsFromArns), arg0, arg1) +} + +// StartReportCreation mocks base method. +func (m *MockTagging) StartReportCreation(arg0 *resourcegroupstaggingapi.StartReportCreationInput) (*resourcegroupstaggingapi.StartReportCreationOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartReportCreation", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.StartReportCreationOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StartReportCreation indicates an expected call of StartReportCreation. +func (mr *MockTaggingMockRecorder) StartReportCreation(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartReportCreation", reflect.TypeOf((*MockTagging)(nil).StartReportCreation), arg0) +} + +// StartReportCreationRequest mocks base method. +func (m *MockTagging) StartReportCreationRequest(arg0 *resourcegroupstaggingapi.StartReportCreationInput) (*request.Request, *resourcegroupstaggingapi.StartReportCreationOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartReportCreationRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.StartReportCreationOutput) + return ret0, ret1 +} + +// StartReportCreationRequest indicates an expected call of StartReportCreationRequest. +func (mr *MockTaggingMockRecorder) StartReportCreationRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartReportCreationRequest", reflect.TypeOf((*MockTagging)(nil).StartReportCreationRequest), arg0) +} + +// StartReportCreationWithContext mocks base method. +func (m *MockTagging) StartReportCreationWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.StartReportCreationInput, arg2 ...request.Option) (*resourcegroupstaggingapi.StartReportCreationOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "StartReportCreationWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.StartReportCreationOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StartReportCreationWithContext indicates an expected call of StartReportCreationWithContext. +func (mr *MockTaggingMockRecorder) StartReportCreationWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartReportCreationWithContext", reflect.TypeOf((*MockTagging)(nil).StartReportCreationWithContext), varargs...) +} + +// TagResources mocks base method. +func (m *MockTagging) TagResources(arg0 *resourcegroupstaggingapi.TagResourcesInput) (*resourcegroupstaggingapi.TagResourcesOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TagResources", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.TagResourcesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TagResources indicates an expected call of TagResources. +func (mr *MockTaggingMockRecorder) TagResources(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagResources", reflect.TypeOf((*MockTagging)(nil).TagResources), arg0) +} + +// TagResourcesRequest mocks base method. +func (m *MockTagging) TagResourcesRequest(arg0 *resourcegroupstaggingapi.TagResourcesInput) (*request.Request, *resourcegroupstaggingapi.TagResourcesOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TagResourcesRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.TagResourcesOutput) + return ret0, ret1 +} + +// TagResourcesRequest indicates an expected call of TagResourcesRequest. +func (mr *MockTaggingMockRecorder) TagResourcesRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagResourcesRequest", reflect.TypeOf((*MockTagging)(nil).TagResourcesRequest), arg0) +} + +// TagResourcesWithContext mocks base method. +func (m *MockTagging) TagResourcesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.TagResourcesInput, arg2 ...request.Option) (*resourcegroupstaggingapi.TagResourcesOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "TagResourcesWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.TagResourcesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TagResourcesWithContext indicates an expected call of TagResourcesWithContext. +func (mr *MockTaggingMockRecorder) TagResourcesWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagResourcesWithContext", reflect.TypeOf((*MockTagging)(nil).TagResourcesWithContext), varargs...) +} + +// UntagResources mocks base method. +func (m *MockTagging) UntagResources(arg0 *resourcegroupstaggingapi.UntagResourcesInput) (*resourcegroupstaggingapi.UntagResourcesOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UntagResources", arg0) + ret0, _ := ret[0].(*resourcegroupstaggingapi.UntagResourcesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UntagResources indicates an expected call of UntagResources. +func (mr *MockTaggingMockRecorder) UntagResources(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UntagResources", reflect.TypeOf((*MockTagging)(nil).UntagResources), arg0) +} + +// UntagResourcesRequest mocks base method. +func (m *MockTagging) UntagResourcesRequest(arg0 *resourcegroupstaggingapi.UntagResourcesInput) (*request.Request, *resourcegroupstaggingapi.UntagResourcesOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UntagResourcesRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*resourcegroupstaggingapi.UntagResourcesOutput) + return ret0, ret1 +} + +// UntagResourcesRequest indicates an expected call of UntagResourcesRequest. +func (mr *MockTaggingMockRecorder) UntagResourcesRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UntagResourcesRequest", reflect.TypeOf((*MockTagging)(nil).UntagResourcesRequest), arg0) +} + +// UntagResourcesWithContext mocks base method. +func (m *MockTagging) UntagResourcesWithContext(arg0 context.Context, arg1 *resourcegroupstaggingapi.UntagResourcesInput, arg2 ...request.Option) (*resourcegroupstaggingapi.UntagResourcesOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UntagResourcesWithContext", varargs...) + ret0, _ := ret[0].(*resourcegroupstaggingapi.UntagResourcesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UntagResourcesWithContext indicates an expected call of UntagResourcesWithContext. +func (mr *MockTaggingMockRecorder) UntagResourcesWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UntagResourcesWithContext", reflect.TypeOf((*MockTagging)(nil).UntagResourcesWithContext), varargs...) +} diff --git a/pkg/aws/services/vpclattice.go b/pkg/aws/services/vpclattice.go index 9e291515..4335b9cf 100644 --- a/pkg/aws/services/vpclattice.go +++ b/pkg/aws/services/vpclattice.go @@ -5,11 +5,14 @@ import ( "errors" "fmt" "os" + "time" "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/hashicorp/golang-lru/v2/expirable" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/vpclattice" "github.com/aws/aws-sdk-go/service/vpclattice/vpclatticeiface" @@ -17,8 +20,6 @@ import ( //go:generate mockgen -destination vpclattice_mocks.go -package services github.com/aws/aws-application-networking-k8s/pkg/aws/services Lattice -type Tags = map[string]*string - type ServiceNetworkInfo struct { SvcNetwork vpclattice.ServiceNetworkSummary Tags Tags @@ -106,10 +107,11 @@ type Lattice interface { type defaultLattice struct { vpclatticeiface.VPCLatticeAPI + + cache *expirable.LRU[string, any] } func NewDefaultLattice(sess *session.Session, region string) *defaultLattice { - var latticeSess vpclatticeiface.VPCLatticeAPI latticeEndpoint := "https://vpc-lattice." + region + ".amazonaws.com" endpoint := os.Getenv("LATTICE_ENDPOINT") @@ -118,9 +120,11 @@ func NewDefaultLattice(sess *session.Session, region string) *defaultLattice { endpoint = latticeEndpoint } - latticeSess = vpclattice.New(sess, aws.NewConfig().WithRegion(region).WithEndpoint(endpoint).WithMaxRetries(20)) + latticeSess := vpclattice.New(sess, aws.NewConfig().WithRegion(region).WithEndpoint(endpoint).WithMaxRetries(20)) - return &defaultLattice{latticeSess} + cache := expirable.NewLRU[string, any](1000, nil, time.Second*60) + + return &defaultLattice{VPCLatticeAPI: latticeSess, cache: cache} } func (d *defaultLattice) GetRulesAsList(ctx context.Context, input *vpclattice.ListRulesInput) ([]*vpclattice.GetRuleOutput, error) { @@ -224,6 +228,36 @@ func (d *defaultLattice) ListTargetGroupsAsList(ctx context.Context, input *vpcl return result, nil } +func (d *defaultLattice) ListTagsForResourceWithContext(ctx context.Context, input *vpclattice.ListTagsForResourceInput, option ...request.Option) (*vpclattice.ListTagsForResourceOutput, error) { + key := tagCacheKey(*input.ResourceArn) + if d.cache != nil { + r, ok := d.cache.Get(key) + if ok { + return r.(*vpclattice.ListTagsForResourceOutput), nil + } + } + out, err := d.VPCLatticeAPI.ListTagsForResourceWithContext(ctx, input, option...) + if err != nil { + return nil, err + } + if d.cache != nil { + d.cache.Add(key, out) + } + return out, nil +} + +func tagCacheKey(arn string) string { + return "tag-" + arn +} + +func (d *defaultLattice) TagResourceWithContext(ctx context.Context, input *vpclattice.TagResourceInput, option ...request.Option) (*vpclattice.TagResourceOutput, error) { + if d.cache != nil { + key := tagCacheKey(*input.ResourceArn) + d.cache.Remove(key) + } + return d.VPCLatticeAPI.TagResourceWithContext(ctx, input, option...) +} + func (d *defaultLattice) ListTargetsAsList(ctx context.Context, input *vpclattice.ListTargetsInput) ([]*vpclattice.TargetSummary, error) { result := []*vpclattice.TargetSummary{} diff --git a/pkg/deploy/lattice/rule_synthesizer.go b/pkg/deploy/lattice/rule_synthesizer.go index da628055..25dca48b 100644 --- a/pkg/deploy/lattice/rule_synthesizer.go +++ b/pkg/deploy/lattice/rule_synthesizer.go @@ -86,21 +86,17 @@ func (r *ruleSynthesizer) findSvcExportTG(ctx context.Context, svcImportTg model } for _, tg := range tgs { - if tg.targetGroupTags == nil { - continue - } - - tgTags := model.TGTagFieldsFromTags(tg.targetGroupTags.Tags) + tgTags := model.TGTagFieldsFromTags(tg.tags) svcMatch := tgTags.IsSourceTypeServiceExport() && (tgTags.K8SServiceName == svcImportTg.K8SServiceName) && (tgTags.K8SServiceNamespace == svcImportTg.K8SServiceNamespace) clusterMatch := (svcImportTg.K8SClusterName == "") || (tgTags.K8SClusterName == svcImportTg.K8SClusterName) - vpcMatch := (svcImportTg.VpcId == "") || (svcImportTg.VpcId == aws.StringValue(tg.getTargetGroupOutput.Config.VpcIdentifier)) + vpcMatch := (svcImportTg.VpcId == "") || (svcImportTg.VpcId == aws.StringValue(tg.tgSummary.VpcIdentifier)) if svcMatch && clusterMatch && vpcMatch { - return *tg.getTargetGroupOutput.Id, nil + return *tg.tgSummary.Id, nil } } diff --git a/pkg/deploy/lattice/rule_synthesizer_test.go b/pkg/deploy/lattice/rule_synthesizer_test.go index 0fe1a82b..ef9f53b6 100644 --- a/pkg/deploy/lattice/rule_synthesizer_test.go +++ b/pkg/deploy/lattice/rule_synthesizer_test.go @@ -175,20 +175,18 @@ func Test_resolveRuleTgs(t *testing.T) { mockTgMgr.EXPECT().List(ctx).Return( []tgListOutput{ { - getTargetGroupOutput: vpclattice.GetTargetGroupOutput{ - Arn: aws.String("svc-export-tg-arn"), - Config: &vpclattice.TargetGroupConfig{ - VpcIdentifier: aws.String("vpc-id"), - }, - Id: aws.String("svc-export-tg-id"), - Name: aws.String("svc-export-tg-name"), + tgSummary: &vpclattice.TargetGroupSummary{ + Arn: aws.String("svc-export-tg-arn"), + VpcIdentifier: aws.String("vpc-id"), + Id: aws.String("svc-export-tg-id"), + Name: aws.String("svc-export-tg-name"), }, - targetGroupTags: &vpclattice.ListTagsForResourceOutput{Tags: map[string]*string{ + tags: map[string]*string{ model.K8SServiceNameKey: aws.String("svc-name"), model.K8SServiceNamespaceKey: aws.String("ns"), model.K8SClusterNameKey: aws.String("cluster-name"), model.K8SSourceTypeKey: aws.String(string(model.SourceTypeSvcExport)), - }}, + }, }, }, nil) diff --git a/pkg/deploy/lattice/target_group_manager.go b/pkg/deploy/lattice/target_group_manager.go index b89dcc44..2691eeaa 100644 --- a/pkg/deploy/lattice/target_group_manager.go +++ b/pkg/deploy/lattice/target_group_manager.go @@ -5,12 +5,10 @@ import ( "errors" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/vpclattice" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "github.com/aws/aws-application-networking-k8s/pkg/aws/services" "github.com/aws/aws-application-networking-k8s/pkg/utils" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/vpclattice" pkg_aws "github.com/aws/aws-application-networking-k8s/pkg/aws" "github.com/aws/aws-application-networking-k8s/pkg/config" @@ -83,6 +81,7 @@ func (s *defaultTargetGroupManager) create(ctx context.Context, modelTg *model.T createInput.Tags[model.K8SServiceNameKey] = &modelTg.Spec.K8SServiceName createInput.Tags[model.K8SServiceNamespaceKey] = &modelTg.Spec.K8SServiceNamespace createInput.Tags[model.K8SSourceTypeKey] = aws.String(string(modelTg.Spec.K8SSourceType)) + createInput.Tags[model.K8SProtocolVersionKey] = &modelTg.Spec.ProtocolVersion if modelTg.Spec.IsSourceTypeRoute() { createInput.Tags[model.K8SRouteNameKey] = &modelTg.Spec.K8SRouteName @@ -113,7 +112,7 @@ func (s *defaultTargetGroupManager) create(ctx context.Context, modelTg *model.T Id: aws.StringValue(resp.Id)}, nil } -func (s *defaultTargetGroupManager) update(ctx context.Context, targetGroup *model.TargetGroup, latticeTgSummary *vpclattice.TargetGroupSummary) (model.TargetGroupStatus, error) { +func (s *defaultTargetGroupManager) update(ctx context.Context, targetGroup *model.TargetGroup, latticeTg *vpclattice.GetTargetGroupOutput) (model.TargetGroupStatus, error) { healthCheckConfig := targetGroup.Spec.HealthCheckConfig if healthCheckConfig == nil { @@ -124,19 +123,19 @@ func (s *defaultTargetGroupManager) update(ctx context.Context, targetGroup *mod _, err := s.cloud.Lattice().UpdateTargetGroupWithContext(ctx, &vpclattice.UpdateTargetGroupInput{ HealthCheck: healthCheckConfig, - TargetGroupIdentifier: latticeTgSummary.Id, + TargetGroupIdentifier: latticeTg.Id, }) if err != nil { return model.TargetGroupStatus{}, - fmt.Errorf("Failed UpdateTargetGroup %s due to %s", aws.StringValue(latticeTgSummary.Id), err) + fmt.Errorf("Failed UpdateTargetGroup %s due to %s", aws.StringValue(latticeTg.Id), err) } - s.log.Infof("Success UpdateTargetGroup %s", aws.StringValue(latticeTgSummary.Id)) + s.log.Infof("Success UpdateTargetGroup %s", aws.StringValue(latticeTg.Id)) modelTgStatus := model.TargetGroupStatus{ - Name: aws.StringValue(latticeTgSummary.Name), - Arn: aws.StringValue(latticeTgSummary.Arn), - Id: aws.StringValue(latticeTgSummary.Id), + Name: aws.StringValue(latticeTg.Name), + Arn: aws.StringValue(latticeTg.Arn), + Id: aws.StringValue(latticeTg.Id), } return modelTgStatus, nil @@ -238,47 +237,36 @@ func (s *defaultTargetGroupManager) Delete(ctx context.Context, modelTg *model.T } type tgListOutput struct { - getTargetGroupOutput vpclattice.GetTargetGroupOutput - targetGroupTags *vpclattice.ListTagsForResourceOutput + tgSummary *vpclattice.TargetGroupSummary + tags services.Tags } // Retrieve all TGs in the account, including tags. If individual tags fetch fails, tags will be nil for that tg func (s *defaultTargetGroupManager) List(ctx context.Context) ([]tgListOutput, error) { lattice := s.cloud.Lattice() var tgList []tgListOutput - targetGroupListInput := vpclattice.ListTargetGroupsInput{} + targetGroupListInput := vpclattice.ListTargetGroupsInput{ + VpcIdentifier: aws.String(config.VpcID), + TargetGroupType: aws.String(vpclattice.TargetGroupTypeIp), + } resp, err := lattice.ListTargetGroupsAsList(ctx, &targetGroupListInput) if err != nil { return nil, err } + if len(resp) == 0 { + return nil, nil + } + tgArns := utils.SliceMap(resp, func(tg *vpclattice.TargetGroupSummary) *string { return tg.Arn }) + tgArnToTagsMap, err := s.cloud.Tagging().GetTagsFromArns(ctx, tgArns) + if err != nil { + return nil, err + } for _, tg := range resp { - tgInput := vpclattice.GetTargetGroupInput{ - TargetGroupIdentifier: tg.Id, - } - - tgOutput, err := lattice.GetTargetGroupWithContext(ctx, &tgInput) - if err != nil { - continue - } - - if tgOutput.Config != nil && aws.StringValue(tgOutput.Config.VpcIdentifier) == config.VpcID { - tagsInput := vpclattice.ListTagsForResourceInput{ - ResourceArn: tg.Arn, - } - - tagsOutput, err := lattice.ListTagsForResourceWithContext(ctx, &tagsInput) - if err != nil { - s.log.Infof("Failed ListTags %s: %s", aws.StringValue(tg.Arn), err) - // setting it to nil, so the caller knows this failed - tagsOutput = nil - } - tgOutput := tgListOutput{ - getTargetGroupOutput: *tgOutput, - targetGroupTags: tagsOutput, - } - tgList = append(tgList, tgOutput) - } + tgList = append(tgList, tgListOutput{ + tgSummary: tg, + tags: tgArnToTagsMap[*tg.Arn], + }) } return tgList, err } @@ -286,50 +274,61 @@ func (s *defaultTargetGroupManager) List(ctx context.Context) ([]tgListOutput, e func (s *defaultTargetGroupManager) findTargetGroup( ctx context.Context, modelTargetGroup *model.TargetGroup, -) (*vpclattice.TargetGroupSummary, error) { - listInput := vpclattice.ListTargetGroupsInput{ - VpcIdentifier: aws.String(modelTargetGroup.Spec.VpcId), - } - resp, err := s.cloud.Lattice().ListTargetGroupsAsList(ctx, &listInput) +) (*vpclattice.GetTargetGroupOutput, error) { + arn, err := s.cloud.Tagging().FindResourceWithTags(ctx, services.ResourceTypeTargetGroup, + model.TagsFromTGTagFields(modelTargetGroup.Spec.TargetGroupTagFields)) if err != nil { return nil, err } + if arn == nil { + return nil, nil + } - for _, latticeTg := range resp { - // we ignore create failed status, so may as well check for it first - status := aws.StringValue(latticeTg.Status) - if status == vpclattice.TargetGroupStatusCreateFailed { - continue + latticeTg, err := s.cloud.Lattice().GetTargetGroupWithContext(ctx, &vpclattice.GetTargetGroupInput{ + TargetGroupIdentifier: arn, + }) + if err != nil { + if services.IsLatticeAPINotFoundErr(err) { + return nil, nil } + return nil, err + } - isMatch, err := s.IsTargetGroupMatch(ctx, modelTargetGroup, latticeTg, nil) - if err != nil { - return nil, err - } - if isMatch { - s.log.Debugf("Target group %s already exists with arn %s", *latticeTg.Name, *latticeTg.Arn) - switch status { - case vpclattice.TargetGroupStatusCreateInProgress: - return nil, errors.New(LATTICE_RETRY) - case vpclattice.TargetGroupStatusActive: - return latticeTg, nil - case vpclattice.TargetGroupStatusDeleteFailed: - return latticeTg, nil - case vpclattice.TargetGroupStatusDeleteInProgress: - return nil, errors.New(LATTICE_RETRY) - } + // we ignore create failed status, so may as well check for it first + status := aws.StringValue(latticeTg.Status) + if status == vpclattice.TargetGroupStatusCreateFailed { + return nil, nil + } + + // Double-check the immutable fields to ensure TG is valid + match, err := s.IsTargetGroupMatch(ctx, modelTargetGroup, &vpclattice.TargetGroupSummary{ + Arn: latticeTg.Arn, + Port: latticeTg.Config.Port, + Protocol: latticeTg.Config.Protocol, + IpAddressType: latticeTg.Config.IpAddressType, + Type: latticeTg.Type, + VpcIdentifier: latticeTg.Config.VpcIdentifier, + }, nil) // we already know that tags match + if err != nil { + return nil, err + } + if match { + switch status { + case vpclattice.TargetGroupStatusCreateInProgress, vpclattice.TargetGroupStatusDeleteInProgress: + return nil, errors.New(LATTICE_RETRY) + case vpclattice.TargetGroupStatusDeleteFailed, vpclattice.TargetGroupStatusActive: + return latticeTg, nil } } return nil, nil } -// latticeTags will be fetched if nil +// Skips tag verification if not provided func (s *defaultTargetGroupManager) IsTargetGroupMatch(ctx context.Context, modelTg *model.TargetGroup, latticeTg *vpclattice.TargetGroupSummary, latticeTagsAsModelTags *model.TargetGroupTagFields) (bool, error) { - // first check fields we have before we try tags if aws.Int64Value(latticeTg.Port) != int64(modelTg.Spec.Port) || aws.StringValue(latticeTg.Protocol) != modelTg.Spec.Protocol || aws.StringValue(latticeTg.IpAddressType) != modelTg.Spec.IpAddressType || @@ -339,38 +338,14 @@ func (s *defaultTargetGroupManager) IsTargetGroupMatch(ctx context.Context, return false, nil } - // so far so good, now we check tags - if latticeTagsAsModelTags == nil { - // fetch the tags if we don't have them already - req := vpclattice.ListTagsForResourceInput{ResourceArn: latticeTg.Arn} - res, err := s.cloud.Lattice().ListTagsForResourceWithContext(ctx, &req) - if err != nil { - if apierrors.IsNotFound(err) { - // may have been deleted in the meantime, that's OK - return false, nil - } - - return false, err + if latticeTagsAsModelTags != nil { + tagsMatch := model.TagFieldsMatch(modelTg.Spec, *latticeTagsAsModelTags) + if !tagsMatch { + return false, nil } - - tags := model.TGTagFieldsFromTags(res.Tags) - latticeTagsAsModelTags = &tags - } - - tagsMatch := model.TagFieldsMatch(modelTg.Spec, *latticeTagsAsModelTags) - if !tagsMatch { - return false, nil - } - - // one last check - ProtocolVersion is not present on TargetGroupSummary, so we have to do a Get - gtgInput := vpclattice.GetTargetGroupInput{TargetGroupIdentifier: latticeTg.Id} - gtgOutput, err := s.cloud.Lattice().GetTargetGroupWithContext(ctx, >gInput) - if err != nil { - return false, err } - pvMatch := aws.StringValue(gtgOutput.Config.ProtocolVersion) == modelTg.Spec.ProtocolVersion - return pvMatch, nil + return true, nil } // Get default health check configuration according to diff --git a/pkg/deploy/lattice/target_group_manager_test.go b/pkg/deploy/lattice/target_group_manager_test.go index 03ef8e8c..c9b40766 100644 --- a/pkg/deploy/lattice/target_group_manager_test.go +++ b/pkg/deploy/lattice/target_group_manager_test.go @@ -4,18 +4,20 @@ import ( "context" "errors" "fmt" + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/vpclattice" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + pkg_aws "github.com/aws/aws-application-networking-k8s/pkg/aws" mocks "github.com/aws/aws-application-networking-k8s/pkg/aws/services" "github.com/aws/aws-application-networking-k8s/pkg/config" "github.com/aws/aws-application-networking-k8s/pkg/model/core" model "github.com/aws/aws-application-networking-k8s/pkg/model/lattice" "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/vpclattice" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - "reflect" - "testing" ) // target group does not exist, and is active after creation @@ -27,7 +29,8 @@ func Test_CreateTargetGroup_TGNotExist_Active(t *testing.T) { config.VpcID = "vpc-id" config.ClusterName = "cluster-name" mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgTypes := [2]string{"by-backendref", "by-serviceexport"} @@ -72,6 +75,7 @@ func Test_CreateTargetGroup_TGNotExist_Active(t *testing.T) { expectedTags[model.K8SServiceNameKey] = &tgSpec.K8SServiceName expectedTags[model.K8SServiceNamespaceKey] = &tgSpec.K8SServiceNamespace expectedTags[model.K8SClusterNameKey] = &tgSpec.K8SClusterName + expectedTags[model.K8SProtocolVersionKey] = &tgSpec.ProtocolVersion if tgType == "by-serviceexport" { value := string(model.SourceTypeSvcExport) @@ -83,9 +87,7 @@ func Test_CreateTargetGroup_TGNotExist_Active(t *testing.T) { expectedTags[model.K8SRouteNamespaceKey] = &tgSpec.K8SRouteNamespace } - var listTgOutput []*vpclattice.TargetGroupSummary - - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).DoAndReturn( func(ctx context.Context, input *vpclattice.CreateTargetGroupInput, arg3 ...interface{}) (*vpclattice.CreateTargetGroupOutput, error) { assert.Equal(t, aws.Int64(int64(tgSpec.Port)), input.Config.Port) @@ -120,7 +122,8 @@ func Test_CreateTargetGroup_TGFailed_Active(t *testing.T) { ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgSpec := model.TargetGroupSpec{} tgSpec.K8SRouteName = "route1" @@ -142,15 +145,17 @@ func Test_CreateTargetGroup_TGFailed_Active(t *testing.T) { } beforeCreateStatus := vpclattice.TargetGroupStatusCreateFailed - tgSummary := vpclattice.TargetGroupSummary{ + tgOutput := vpclattice.GetTargetGroupOutput{ Arn: &arn, Id: &id, Name: &name, Status: &beforeCreateStatus, + Config: &vpclattice.TargetGroupConfig{}, } - listTgOutput := []*vpclattice.TargetGroupSummary{&tgSummary} - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(&arn, nil) + mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) + mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return(tgCreateOutput, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) resp, err := tgManager.Upsert(ctx, &tgCreateInput) @@ -196,7 +201,8 @@ func Test_CreateTargetGroup_TGActive_UpdateHealthCheck(t *testing.T) { defer c.Finish() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgSpec := model.TargetGroupSpec{ Port: 80, @@ -210,30 +216,20 @@ func Test_CreateTargetGroup_TGActive_UpdateHealthCheck(t *testing.T) { Spec: tgSpec, } - tgSummary := vpclattice.TargetGroupSummary{ - Arn: &arn, - Id: &id, - Name: aws.String("test-https-http1"), - Status: aws.String(vpclattice.TargetGroupStatusActive), - Port: aws.Int64(80), - Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + tgOutput := vpclattice.GetTargetGroupOutput{ + Arn: &arn, + Id: &id, + Name: aws.String("test-https-http1"), + Status: aws.String(vpclattice.TargetGroupStatusActive), + Config: &vpclattice.TargetGroupConfig{ + Port: aws.Int64(80), + Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + ProtocolVersion: aws.String(vpclattice.TargetGroupProtocolVersionHttp1), + }, } - listTgOutput := []*vpclattice.TargetGroupSummary{&tgSummary} - - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) - - // empty tags should be OK and should match since all tag values on the spec are empty - mockLattice.EXPECT().ListTagsForResourceWithContext(ctx, gomock.Any()).Return( - &vpclattice.ListTagsForResourceOutput{}, nil) - - // we use Get to do a last check on the protocol version - mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return( - &vpclattice.GetTargetGroupOutput{ - Config: &vpclattice.TargetGroupConfig{ - ProtocolVersion: aws.String(tgSpec.ProtocolVersion), - }, - }, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(&arn, nil) + mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) if tt.wantErr { mockLattice.EXPECT().UpdateTargetGroupWithContext(ctx, gomock.Any()).Return(nil, errors.New("error")) @@ -261,11 +257,13 @@ func Test_CreateTargetGroup_ExistingTG_Status_Retry(t *testing.T) { defer c.Finish() ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgSpec := model.TargetGroupSpec{ - Port: 80, - Protocol: "HTTP", + Port: 80, + Protocol: "HTTP", + ProtocolVersion: "HTTP1", } tgCreateInput := model.TargetGroup{ Spec: tgSpec, @@ -282,25 +280,20 @@ func Test_CreateTargetGroup_ExistingTG_Status_Retry(t *testing.T) { for _, retryStatus := range retryStatuses { t.Run(fmt.Sprintf("retry on status %s", retryStatus), func(t *testing.T) { beforeCreateStatus := retryStatus - tgSummary := vpclattice.TargetGroupSummary{ - Arn: &arn, - Id: &id, - Name: &name, - Status: &beforeCreateStatus, - Port: aws.Int64(80), - Protocol: aws.String("HTTP"), + tgOutput := vpclattice.GetTargetGroupOutput{ + Arn: &arn, + Id: &id, + Name: &name, + Status: &beforeCreateStatus, + Config: &vpclattice.TargetGroupConfig{ + Port: aws.Int64(80), + Protocol: aws.String("HTTP"), + ProtocolVersion: aws.String("HTTP1"), + }, } - listTgOutput := []*vpclattice.TargetGroupSummary{&tgSummary} - - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) - mockLattice.EXPECT().ListTagsForResourceWithContext(ctx, gomock.Any()).Return( - &vpclattice.ListTagsForResourceOutput{}, nil) - mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return( - &vpclattice.GetTargetGroupOutput{ - Config: &vpclattice.TargetGroupConfig{ - ProtocolVersion: aws.String(tgSpec.ProtocolVersion), - }, - }, nil) + + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(&arn, nil) + mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) _, err := tgManager.Upsert(ctx, &tgCreateInput) @@ -316,7 +309,8 @@ func Test_CreateTargetGroup_NewTG_RetryStatus(t *testing.T) { defer c.Finish() ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgSpec := model.TargetGroupSpec{ Port: 80, @@ -330,8 +324,6 @@ func Test_CreateTargetGroup_NewTG_RetryStatus(t *testing.T) { id := "12345678912345678912" name := "test" - var listTgOutput []*vpclattice.TargetGroupSummary - retryStatuses := []string{ vpclattice.TargetGroupStatusDeleteInProgress, vpclattice.TargetGroupStatusCreateFailed, @@ -349,7 +341,7 @@ func Test_CreateTargetGroup_NewTG_RetryStatus(t *testing.T) { Status: &tgStatus, } - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return(tgCreateOutput, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -365,7 +357,8 @@ func Test_Lattice_API_Errors(t *testing.T) { defer c.Finish() ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgSpec := model.TargetGroupSpec{ Port: 80, @@ -375,10 +368,9 @@ func Test_Lattice_API_Errors(t *testing.T) { ResourceMeta: core.ResourceMeta{}, Spec: tgSpec, } - var listTgOutput []*vpclattice.TargetGroupSummary - // list error - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, errors.New("test")) + // search error + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, errors.New("test")) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) _, err := tgManager.Upsert(ctx, &tgCreateInput) @@ -386,7 +378,7 @@ func Test_Lattice_API_Errors(t *testing.T) { assert.Equal(t, errors.New("test"), err) // create error - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return(nil, errors.New("test")) tgManager = NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -434,7 +426,8 @@ func Test_DeleteTG_DeRegisterTargets_DeleteTargetGroup(t *testing.T) { mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) mockLattice.EXPECT().DeregisterTargetsWithContext(ctx, gomock.Any()).Return(deRegisterTargetsOutput, nil) mockLattice.EXPECT().DeleteTargetGroupWithContext(ctx, gomock.Any()).Return(deleteTargetGroupOutput, nil) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -473,7 +466,8 @@ func Test_DeleteTG_NoRegisteredTargets_DeleteTargetGroup(t *testing.T) { mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) mockLattice.EXPECT().DeregisterTargetsWithContext(ctx, gomock.Any()).Return(deRegisterTargetsOutput, nil) mockLattice.EXPECT().DeleteTargetGroupWithContext(ctx, gomock.Any()).Return(deleteTargetGroupOutput, nil) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -487,15 +481,19 @@ func Test_DeleteTG_WithExistingTG(t *testing.T) { defer c.Finish() ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) - - tgSummary := vpclattice.TargetGroupSummary{ - Arn: aws.String("existing-tg-arn"), - Id: aws.String("existing-tg-id"), - Name: aws.String("name"), - Status: aws.String(vpclattice.TargetGroupStatusActive), - Port: aws.Int64(80), - Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) + + tgOutput := vpclattice.GetTargetGroupOutput{ + Arn: aws.String("existing-tg-arn"), + Id: aws.String("existing-tg-id"), + Name: aws.String("name"), + Status: aws.String(vpclattice.TargetGroupStatusActive), + Config: &vpclattice.TargetGroupConfig{ + Port: aws.Int64(80), + Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + ProtocolVersion: aws.String(vpclattice.HealthCheckProtocolVersionHttp1), + }, } tgSpec := model.TargetGroupSpec{ @@ -508,22 +506,13 @@ func Test_DeleteTG_WithExistingTG(t *testing.T) { Status: nil, } var listTargetsOutput []*vpclattice.TargetSummary - listTgOutput := []*vpclattice.TargetGroupSummary{&tgSummary} - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) - mockLattice.EXPECT().ListTagsForResourceWithContext(ctx, gomock.Any()).Return( - &vpclattice.ListTagsForResourceOutput{}, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(tgOutput.Arn, nil) + mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) - mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return( - &vpclattice.GetTargetGroupOutput{ - Config: &vpclattice.TargetGroupConfig{ - ProtocolVersion: aws.String(tgSpec.ProtocolVersion), - }, - }, nil) - - dtgInput := &vpclattice.DeleteTargetGroupInput{TargetGroupIdentifier: tgSummary.Id} + dtgInput := &vpclattice.DeleteTargetGroupInput{TargetGroupIdentifier: tgOutput.Id} dtgOutput := &vpclattice.DeleteTargetGroupOutput{} mockLattice.EXPECT().DeleteTargetGroupWithContext(ctx, dtgInput).Return(dtgOutput, nil) @@ -538,16 +527,8 @@ func Test_DeleteTG_NothingToDelete(t *testing.T) { defer c.Finish() ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) - - tgSummary := vpclattice.TargetGroupSummary{ - Arn: aws.String("existing-tg-arn"), - Id: aws.String("existing-tg-id"), - Name: aws.String("name"), - Status: aws.String(vpclattice.TargetGroupStatusActive), - Port: aws.Int64(443), // <-- important difference, so not a match - Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), - } + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgSpec := model.TargetGroupSpec{ Port: 80, @@ -558,9 +539,8 @@ func Test_DeleteTG_NothingToDelete(t *testing.T) { Spec: tgSpec, Status: nil, } - listTgOutput := []*vpclattice.TargetGroupSummary{&tgSummary} - mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTgOutput, nil) + mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) err := tgManager.Delete(ctx, &tgDeleteInput) @@ -596,7 +576,8 @@ func Test_DeleteTG_DeRegisteredTargetsFailed(t *testing.T) { mockLattice := mocks.NewMockLattice(c) mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) mockLattice.EXPECT().DeregisterTargetsWithContext(ctx, gomock.Any()).Return(deRegisterTargetsOutput, errors.New("Deregister_failed")) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -630,7 +611,8 @@ func Test_DeleteTG_ListTargetsFailed(t *testing.T) { ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, errors.New("Listregister_failed")) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -675,7 +657,8 @@ func Test_DeleteTG_DeRegisterTargetsUnsuccessfully(t *testing.T) { mockLattice := mocks.NewMockLattice(c) mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) mockLattice.EXPECT().DeregisterTargetsWithContext(ctx, gomock.Any()).Return(deRegisterTargetsOutput, nil) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -722,7 +705,8 @@ func Test_DeleteTG_DeRegisterTargets_DeleteTargetGroupFailed(t *testing.T) { mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) mockLattice.EXPECT().DeregisterTargetsWithContext(ctx, gomock.Any()).Return(deRegisterTargetsOutput, nil) mockLattice.EXPECT().DeleteTargetGroupWithContext(ctx, gomock.Any()).Return(deleteTargetGroupOutput, errors.New("DeleteTG_failed")) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -748,43 +732,33 @@ func Test_ListTG_TGsExist(t *testing.T) { } listTGOutput := []*vpclattice.TargetGroupSummary{tg1, tg2} - config1 := &vpclattice.TargetGroupConfig{ - VpcIdentifier: &config.VpcID, - } - getTG1 := &vpclattice.GetTargetGroupOutput{ - Config: config1, - } - - vpcid2 := "123456789" - config2 := &vpclattice.TargetGroupConfig{ - VpcIdentifier: &vpcid2, - } - getTG2 := &vpclattice.GetTargetGroupOutput{ - Config: config2, - } - c := gomock.NewController(t) defer c.Finish() ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTGOutput, nil) - mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(getTG1, nil) + // assume no tags - mockLattice.EXPECT().ListTagsForResourceWithContext(ctx, gomock.Any()).Return(nil, errors.New("no tags")) - mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(getTG2, nil) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + mockTagging.EXPECT().GetTagsFromArns(ctx, gomock.Any()).Return(nil, nil) + + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) tgList, err := tgManager.List(ctx) expect := []tgListOutput{ { - getTargetGroupOutput: *getTG1, - targetGroupTags: nil, + tgSummary: tg1, + tags: nil, + }, + { + tgSummary: tg2, + tags: nil, }, } assert.Nil(t, err) - assert.Equal(t, tgList, expect) + assert.ElementsMatch(t, tgList, expect) } func Test_ListTG_NoTG(t *testing.T) { @@ -795,7 +769,8 @@ func Test_ListTG_NoTG(t *testing.T) { ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTGOutput, nil) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) tgList, err := tgManager.List(ctx) @@ -922,8 +897,6 @@ func Test_IsTargetGroupMatch(t *testing.T) { modelTg *model.TargetGroup latticeTg *vpclattice.TargetGroupSummary tags *model.TargetGroupTagFields - listTagsOut *vpclattice.ListTagsForResourceOutput - getTgOut *vpclattice.GetTargetGroupOutput }{ { name: "port not equal", @@ -950,41 +923,7 @@ func Test_IsTargetGroupMatch(t *testing.T) { tags: &model.TargetGroupTagFields{K8SClusterName: "foo"}, }, { - name: "fetch tags not equal", - expectedResult: false, - wantErr: false, - modelTg: &model.TargetGroup{ - Spec: model.TargetGroupSpec{ - Port: 443, - }, - }, - latticeTg: &vpclattice.TargetGroupSummary{Port: aws.Int64(443)}, - listTagsOut: &vpclattice.ListTagsForResourceOutput{ - Tags: map[string]*string{ - model.K8SClusterNameKey: aws.String("foo"), - }, - }, - }, - { - name: "protocol version not equal", - expectedResult: false, - wantErr: false, - modelTg: &model.TargetGroup{ - Spec: model.TargetGroupSpec{ - Port: 443, - ProtocolVersion: "HTTP1", - }, - }, - latticeTg: &vpclattice.TargetGroupSummary{Port: aws.Int64(443)}, - tags: &model.TargetGroupTagFields{}, - getTgOut: &vpclattice.GetTargetGroupOutput{ - Config: &vpclattice.TargetGroupConfig{ - ProtocolVersion: aws.String("HTTP2"), - }, - }, - }, - { - name: "equal with existing tags", + name: "tags equal", expectedResult: true, wantErr: false, modelTg: &model.TargetGroup{ @@ -1000,14 +939,9 @@ func Test_IsTargetGroupMatch(t *testing.T) { tags: &model.TargetGroupTagFields{ K8SClusterName: "cluster", }, - getTgOut: &vpclattice.GetTargetGroupOutput{ - Config: &vpclattice.TargetGroupConfig{ - ProtocolVersion: aws.String("HTTP1"), - }, - }, }, { - name: "equal with fetched tags", + name: "tags not provided", expectedResult: true, wantErr: false, modelTg: &model.TargetGroup{ @@ -1020,16 +954,6 @@ func Test_IsTargetGroupMatch(t *testing.T) { }, }, latticeTg: &vpclattice.TargetGroupSummary{Port: aws.Int64(443)}, - listTagsOut: &vpclattice.ListTagsForResourceOutput{ - Tags: map[string]*string{ - model.K8SClusterNameKey: aws.String("cluster"), - }, - }, - getTgOut: &vpclattice.GetTargetGroupOutput{ - Config: &vpclattice.TargetGroupConfig{ - ProtocolVersion: aws.String("HTTP1"), - }, - }, }, } @@ -1040,14 +964,8 @@ func Test_IsTargetGroupMatch(t *testing.T) { ctx := context.TODO() mockLattice := mocks.NewMockLattice(c) - cloud := pkg_aws.NewDefaultCloud(mockLattice, TestCloudConfig) - - if tt.listTagsOut != nil { - mockLattice.EXPECT().ListTagsForResourceWithContext(ctx, gomock.Any()).Return(tt.listTagsOut, nil) - } - if tt.getTgOut != nil { - mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(tt.getTgOut, nil) - } + mockTagging := mocks.NewMockTagging(c) + cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) s := NewTargetGroupManager(gwlog.FallbackLogger, cloud) result, err := s.IsTargetGroupMatch(ctx, tt.modelTg, tt.latticeTg, tt.tags) diff --git a/pkg/deploy/lattice/target_group_synthesizer.go b/pkg/deploy/lattice/target_group_synthesizer.go index 58dfa4d8..2733a751 100644 --- a/pkg/deploy/lattice/target_group_synthesizer.go +++ b/pkg/deploy/lattice/target_group_synthesizer.go @@ -11,7 +11,6 @@ import ( anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1" "github.com/aws/aws-application-networking-k8s/pkg/gateway" - "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" "k8s.io/apimachinery/pkg/types" @@ -127,9 +126,9 @@ func (t *TargetGroupSynthesizer) SynthesizeUnusedDelete(ctx context.Context) err var retErr error for _, tg := range tgsToDelete { modelStatus := model.TargetGroupStatus{ - Name: aws.StringValue(tg.getTargetGroupOutput.Name), - Arn: aws.StringValue(tg.getTargetGroupOutput.Arn), - Id: aws.StringValue(tg.getTargetGroupOutput.Id), + Name: aws.StringValue(tg.tgSummary.Name), + Arn: aws.StringValue(tg.tgSummary.Arn), + Id: aws.StringValue(tg.tgSummary.Id), } modelTg := model.TargetGroup{ Status: &modelStatus, @@ -168,15 +167,15 @@ func (t *TargetGroupSynthesizer) calculateTargetGroupsToDelete(ctx context.Conte // TGs from earlier releases will require 1-time manual cleanup // this method of validation only covers TGs created by this build // of the controller forward - tagFields := model.TGTagFieldsFromTags(latticeTg.targetGroupTags.Tags) + tagFields := model.TGTagFieldsFromTags(latticeTg.tags) if !t.hasExpectedTags(latticeTg, tagFields) { continue } // most importantly, is the tg in use? - if len(latticeTg.getTargetGroupOutput.ServiceArns) > 0 { + if len(latticeTg.tgSummary.ServiceArns) > 0 { t.log.Debugf("TargetGroup %s (%s) is referenced by lattice service", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) continue } @@ -202,7 +201,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteSvcExportTg( } t.log.Debugf("TargetGroup %s (%s) is referenced by ServiceExport", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) svcExport := &anv1alpha1.ServiceExport{} err := t.client.Get(ctx, svcExportName, svcExport) @@ -210,7 +209,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteSvcExportTg( if apierrors.IsNotFound(err) { // if the service export does not exist, we can safely delete t.log.Infof("Will delete TargetGroup %s (%s) - ServiceExport is not found", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return true } else { // skip if we have an unknown error @@ -222,7 +221,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteSvcExportTg( if !svcExport.DeletionTimestamp.IsZero() { // backing object is deleted, we can delete too t.log.Infof("Will delete TargetGroup %s (%s) - ServiceExport has been deleted", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return true } @@ -235,21 +234,21 @@ func (t *TargetGroupSynthesizer) shouldDeleteSvcExportTg( return false } - // tags are already validated, just need to check the other essentials - ltg := latticeTg.getTargetGroupOutput - if int64(modelTg.Spec.Port) != aws.Int64Value(ltg.Config.Port) || - modelTg.Spec.Protocol != aws.StringValue(ltg.Config.Protocol) || - modelTg.Spec.ProtocolVersion != aws.StringValue(ltg.Config.ProtocolVersion) || - modelTg.Spec.IpAddressType != aws.StringValue(ltg.Config.IpAddressType) { + // the main identifiers are validated, just need to check the other essentials. + // protocolVersion is not in TG summary so we are bringing it from tags. + if int64(modelTg.Spec.Port) != aws.Int64Value(latticeTg.tgSummary.Port) || + modelTg.Spec.Protocol != aws.StringValue(latticeTg.tgSummary.Protocol) || + modelTg.Spec.ProtocolVersion != tagFields.K8SProtocolVersion || + modelTg.Spec.IpAddressType != aws.StringValue(latticeTg.tgSummary.IpAddressType) { // one or more immutable fields differ from the source, so the TG is out of date t.log.Infof("Will delete TargetGroup %s (%s) - fields differ from source service/service export", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return true } t.log.Debugf("ServiceExport TargetGroup %s (%s) is up to date", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return false } @@ -264,7 +263,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( var err error var route core.Route - if *latticeTg.getTargetGroupOutput.Config.ProtocolVersion == vpclattice.TargetGroupProtocolVersionGrpc { + if tagFields.K8SProtocolVersion == vpclattice.TargetGroupProtocolVersionGrpc { route, err = core.GetGRPCRoute(ctx, t.client, routeName) } else { route, err = core.GetHTTPRoute(ctx, t.client, routeName) @@ -274,7 +273,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( if apierrors.IsNotFound(err) { // if the route does not exist, we can safely delete t.log.Debugf("Will delete TargetGroup %s (%s) - Route is not found", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return true } else { // skip if we have an unknown error @@ -285,7 +284,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( if !route.DeletionTimestamp().IsZero() { t.log.Debugf("Will delete TargetGroup %s (%s) - Route is deleted", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return true } @@ -305,23 +304,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( var matchFound bool for _, modelTg := range resTargetGroups { - ltg := latticeTg.getTargetGroupOutput - latticeTgSummary := vpclattice.TargetGroupSummary{ - Arn: ltg.Arn, - CreatedAt: ltg.CreatedAt, - Id: ltg.Id, - IpAddressType: ltg.Config.IpAddressType, - LastUpdatedAt: ltg.LastUpdatedAt, - Name: ltg.Name, - Port: ltg.Config.Port, - Protocol: ltg.Config.Protocol, - ServiceArns: ltg.ServiceArns, - Status: ltg.Status, - Type: ltg.Type, - VpcIdentifier: ltg.Config.VpcIdentifier, - } - - match, err := t.targetGroupManager.IsTargetGroupMatch(ctx, modelTg, &latticeTgSummary, &tagFields) + match, err := t.targetGroupManager.IsTargetGroupMatch(ctx, modelTg, latticeTg.tgSummary, &tagFields) if err != nil { t.log.Infof("Received error during tg comparison %s", err) continue @@ -329,7 +312,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( if match { t.log.Debugf("Route TargetGroup %s (%s) is up to date", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) matchFound = true break @@ -338,7 +321,7 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( if !matchFound { t.log.Debugf("Will delete TargetGroup %s (%s) - TG is not up to date", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return true // safe to delete } @@ -347,18 +330,18 @@ func (t *TargetGroupSynthesizer) shouldDeleteRouteTg( } func (t *TargetGroupSynthesizer) hasTags(latticeTg tgListOutput) bool { - if latticeTg.targetGroupTags == nil { + if latticeTg.tags == nil { t.log.Debugf("Ignoring target group %s (%s) because tag fetch was not successful", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return false } return true } func (t *TargetGroupSynthesizer) vpcMatchesConfig(latticeTg tgListOutput) bool { - if aws.StringValue(latticeTg.getTargetGroupOutput.Config.VpcIdentifier) != config.VpcID { + if aws.StringValue(latticeTg.tgSummary.VpcIdentifier) != config.VpcID { t.log.Debugf("Ignoring target group %s (%s) because it is not configured for this VPC", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return false } return true @@ -367,7 +350,7 @@ func (t *TargetGroupSynthesizer) vpcMatchesConfig(latticeTg tgListOutput) bool { func (t *TargetGroupSynthesizer) hasExpectedTags(latticeTg tgListOutput, tagFields model.TargetGroupTagFields) bool { if tagFields.K8SClusterName != config.ClusterName { t.log.Debugf("Ignoring target group %s (%s) because it is not configured for this Cluster", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return false } @@ -375,14 +358,14 @@ func (t *TargetGroupSynthesizer) hasExpectedTags(latticeTg tgListOutput, tagFiel tagFields.K8SServiceName == "" || tagFields.K8SServiceNamespace == "" { t.log.Infof("Ignoring target group %s (%s) as one or more required tags are missing", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return false } // route-based TGs should have the additional route keys if tagFields.IsSourceTypeRoute() && (tagFields.K8SRouteName == "" || tagFields.K8SRouteNamespace == "") { t.log.Infof("Ignoring route-based target group %s (%s) as one or more required tags are missing", - *latticeTg.getTargetGroupOutput.Arn, *latticeTg.getTargetGroupOutput.Name) + *latticeTg.tgSummary.Arn, *latticeTg.tgSummary.Name) return false } diff --git a/pkg/deploy/lattice/target_group_synthesizer_test.go b/pkg/deploy/lattice/target_group_synthesizer_test.go index 1c0e9b8c..86ad629b 100644 --- a/pkg/deploy/lattice/target_group_synthesizer_test.go +++ b/pkg/deploy/lattice/target_group_synthesizer_test.go @@ -2,20 +2,22 @@ package lattice import ( "context" - mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" - "github.com/aws/aws-application-networking-k8s/pkg/config" - "github.com/aws/aws-application-networking-k8s/pkg/gateway" - "github.com/aws/aws-application-networking-k8s/pkg/model/core" - "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" + "net/http" + "testing" + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/vpclattice" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "net/http" "sigs.k8s.io/controller-runtime/pkg/client" - "testing" - "time" + + mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" + "github.com/aws/aws-application-networking-k8s/pkg/config" + "github.com/aws/aws-application-networking-k8s/pkg/gateway" + "github.com/aws/aws-application-networking-k8s/pkg/model/core" + "github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" @@ -53,35 +55,26 @@ func Test_Synthesize(t *testing.T) { } func copy(src tgListOutput) tgListOutput { - srcgto := src.getTargetGroupOutput + srcSummary := src.tgSummary cp := tgListOutput{ - getTargetGroupOutput: vpclattice.GetTargetGroupOutput{ - Arn: aws.String(aws.StringValue(srcgto.Arn)), - Config: nil, - Id: aws.String(aws.StringValue(srcgto.Id)), - Name: aws.String(aws.StringValue(srcgto.Name)), - Type: aws.String(aws.StringValue(srcgto.Type)), - CreatedAt: aws.Time(aws.TimeValue(srcgto.CreatedAt)), + tgSummary: &vpclattice.TargetGroupSummary{ + Arn: aws.String(aws.StringValue(srcSummary.Arn)), + Id: aws.String(aws.StringValue(srcSummary.Id)), + Name: aws.String(aws.StringValue(srcSummary.Name)), + Type: aws.String(aws.StringValue(srcSummary.Type)), + CreatedAt: aws.Time(aws.TimeValue(srcSummary.CreatedAt)), + IpAddressType: aws.String(aws.StringValue(srcSummary.IpAddressType)), + Port: aws.Int64(aws.Int64Value(srcSummary.Port)), + Protocol: aws.String(aws.StringValue(srcSummary.Protocol)), + VpcIdentifier: aws.String(aws.StringValue(srcSummary.VpcIdentifier)), }, } - if srcgto.Config != nil { - cp.getTargetGroupOutput.Config = &vpclattice.TargetGroupConfig{ - IpAddressType: aws.String(aws.StringValue(srcgto.Config.IpAddressType)), - Port: aws.Int64(aws.Int64Value(srcgto.Config.Port)), - Protocol: aws.String(aws.StringValue(srcgto.Config.Protocol)), - ProtocolVersion: aws.String(aws.StringValue(srcgto.Config.ProtocolVersion)), - VpcIdentifier: aws.String(aws.StringValue(srcgto.Config.VpcIdentifier)), - } - } - - srctags := src.targetGroupTags + srctags := src.tags if srctags != nil { - cp.targetGroupTags = &vpclattice.ListTagsForResourceOutput{ - Tags: make(map[string]*string), - } - for k, v := range srctags.Tags { - cp.targetGroupTags.Tags[k] = aws.String(aws.StringValue(v)) + cp.tags = make(map[string]*string) + for k, v := range srctags { + cp.tags[k] = aws.String(aws.StringValue(v)) } } @@ -110,47 +103,46 @@ func Test_SynthesizeUnusedDeleteIgnoreNotManagedByController(t *testing.T) { var nonManagedTgs []tgListOutput tgTagFetchUnsuccessful := tgListOutput{ - getTargetGroupOutput: vpclattice.GetTargetGroupOutput{ - Arn: aws.String("tg-arn"), - Id: aws.String("tg-id"), - Name: aws.String("tg-name"), - Config: &vpclattice.TargetGroupConfig{}, - Type: aws.String("IP"), + tgSummary: &vpclattice.TargetGroupSummary{ + Arn: aws.String("tg-arn"), + Id: aws.String("tg-id"), + Name: aws.String("tg-name"), + Type: aws.String("IP"), }, - targetGroupTags: nil, + tags: nil, } nonManagedTgs = append(nonManagedTgs, tgTagFetchUnsuccessful) tgWrongVpc := copy(tgTagFetchUnsuccessful) - tgWrongVpc.targetGroupTags = &vpclattice.ListTagsForResourceOutput{Tags: make(map[string]*string)} - tgWrongVpc.getTargetGroupOutput.Config.VpcIdentifier = aws.String("another-vpc") + tgWrongVpc.tags = make(map[string]*string) + tgWrongVpc.tgSummary.VpcIdentifier = aws.String("another-vpc") nonManagedTgs = append(nonManagedTgs, tgWrongVpc) tgWrongCluster := copy(tgWrongVpc) - tgWrongCluster.getTargetGroupOutput.Config.VpcIdentifier = aws.String("vpc-id") - tgWrongCluster.targetGroupTags.Tags[model.K8SClusterNameKey] = aws.String("another-cluster") + tgWrongCluster.tgSummary.VpcIdentifier = aws.String("vpc-id") + tgWrongCluster.tags[model.K8SClusterNameKey] = aws.String("another-cluster") nonManagedTgs = append(nonManagedTgs, tgWrongCluster) tgInvalidParentRef := copy(tgWrongCluster) - tgInvalidParentRef.targetGroupTags.Tags[model.K8SClusterNameKey] = aws.String("cluster-name") - tgInvalidParentRef.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeInvalid)) + tgInvalidParentRef.tags[model.K8SClusterNameKey] = aws.String("cluster-name") + tgInvalidParentRef.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeInvalid)) nonManagedTgs = append(nonManagedTgs, tgInvalidParentRef) tgMissingK8SServiceName := copy(tgInvalidParentRef) - tgMissingK8SServiceName.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeSvcExport)) + tgMissingK8SServiceName.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeSvcExport)) nonManagedTgs = append(nonManagedTgs, tgMissingK8SServiceName) tgMissingK8SServiceNamespace := copy(tgMissingK8SServiceName) - tgMissingK8SServiceNamespace.targetGroupTags.Tags[model.K8SServiceNameKey] = aws.String("my-service") + tgMissingK8SServiceNamespace.tags[model.K8SServiceNameKey] = aws.String("my-service") nonManagedTgs = append(nonManagedTgs, tgMissingK8SServiceNamespace) tgMissingRouteName := copy(tgMissingK8SServiceNamespace) - tgMissingRouteName.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeHTTPRoute)) - tgMissingRouteName.targetGroupTags.Tags[model.K8SServiceNamespaceKey] = aws.String("ns-1") + tgMissingRouteName.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeHTTPRoute)) + tgMissingRouteName.tags[model.K8SServiceNamespaceKey] = aws.String("ns-1") nonManagedTgs = append(nonManagedTgs, tgMissingRouteName) tgMissingRouteNamespace := copy(tgMissingRouteName) - tgMissingRouteNamespace.targetGroupTags.Tags[model.K8SRouteNameKey] = aws.String("route-name") + tgMissingRouteNamespace.tags[model.K8SRouteNameKey] = aws.String("route-name") nonManagedTgs = append(nonManagedTgs, tgMissingRouteNamespace) mockTGManager.EXPECT().List(ctx).Return(nonManagedTgs, nil) @@ -161,24 +153,22 @@ func Test_SynthesizeUnusedDeleteIgnoreNotManagedByController(t *testing.T) { func getBaseTg() tgListOutput { baseTg := tgListOutput{ - getTargetGroupOutput: vpclattice.GetTargetGroupOutput{ - Arn: aws.String("tg-arn"), - Id: aws.String("tg-id"), - Name: aws.String("tg-name"), - CreatedAt: aws.Time(time.Now()), - Config: &vpclattice.TargetGroupConfig{ - VpcIdentifier: aws.String("vpc-id"), - Port: aws.Int64(80), - Protocol: aws.String("HTTP"), - ProtocolVersion: aws.String("HTTP1"), - IpAddressType: aws.String("IPV4"), - }, + tgSummary: &vpclattice.TargetGroupSummary{ + Arn: aws.String("tg-arn"), + Id: aws.String("tg-id"), + Name: aws.String("tg-name"), + CreatedAt: aws.Time(time.Now()), + VpcIdentifier: aws.String("vpc-id"), + Port: aws.Int64(80), + Protocol: aws.String("HTTP"), + IpAddressType: aws.String("IPV4"), }, - targetGroupTags: &vpclattice.ListTagsForResourceOutput{Tags: make(map[string]*string)}, + tags: make(map[string]*string), } - baseTg.targetGroupTags.Tags[model.K8SClusterNameKey] = aws.String("cluster-name") - baseTg.targetGroupTags.Tags[model.K8SServiceNameKey] = aws.String("svc") - baseTg.targetGroupTags.Tags[model.K8SServiceNamespaceKey] = aws.String("ns") + baseTg.tags[model.K8SClusterNameKey] = aws.String("cluster-name") + baseTg.tags[model.K8SServiceNameKey] = aws.String("svc") + baseTg.tags[model.K8SServiceNamespaceKey] = aws.String("ns") + baseTg.tags[model.K8SProtocolVersionKey] = aws.String("HTTP") return baseTg } @@ -206,20 +196,22 @@ func Test_DoNotDeleteCases(t *testing.T) { var noDeleteTgs []tgListOutput tgWithSvcArns := copy(baseTg) - tgWithSvcArns.getTargetGroupOutput.Arn = aws.String("tg-with-svcs-arn") // useful for reading logs - tgWithSvcArns.getTargetGroupOutput.ServiceArns = []*string{aws.String("svc-arn")} + tgWithSvcArns.tgSummary.Arn = aws.String("tg-with-svcs-arn") // useful for reading logs + tgWithSvcArns.tgSummary.ServiceArns = []*string{aws.String("svc-arn")} noDeleteTgs = append(noDeleteTgs, tgWithSvcArns) tgSvcExportUpToDate := copy(baseTg) - tgSvcExportUpToDate.getTargetGroupOutput.Arn = aws.String("tg-svc-export-arn") - tgSvcExportUpToDate.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeSvcExport)) + tgSvcExportUpToDate.tgSummary.Arn = aws.String("tg-svc-export-arn") + tgSvcExportUpToDate.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeSvcExport)) + tgSvcExportUpToDate.tags[model.K8SProtocolVersionKey] = aws.String("HTTP1") noDeleteTgs = append(noDeleteTgs, tgSvcExportUpToDate) tgSvcUpToDate := copy(baseTg) - tgSvcUpToDate.getTargetGroupOutput.Arn = aws.String("tg-svc-arn") - tgSvcUpToDate.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeHTTPRoute)) - tgSvcUpToDate.targetGroupTags.Tags[model.K8SRouteNameKey] = aws.String("route") - tgSvcUpToDate.targetGroupTags.Tags[model.K8SRouteNamespaceKey] = aws.String("route-ns") + tgSvcUpToDate.tgSummary.Arn = aws.String("tg-svc-arn") + tgSvcUpToDate.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeHTTPRoute)) + tgSvcUpToDate.tags[model.K8SRouteNameKey] = aws.String("route") + tgSvcUpToDate.tags[model.K8SRouteNamespaceKey] = aws.String("route-ns") + tgSvcUpToDate.tags[model.K8SProtocolVersionKey] = aws.String("HTTP1") noDeleteTgs = append(noDeleteTgs, tgSvcUpToDate) mockTGManager.EXPECT().List(ctx).Return(noDeleteTgs, nil) @@ -286,8 +278,9 @@ func Test_DeleteServiceExport_DeleteCases(t *testing.T) { var deleteTgs []tgListOutput tgSvcExport := copy(baseTg) - tgSvcExport.getTargetGroupOutput.Arn = aws.String("tg-svc-export-arn") - tgSvcExport.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeSvcExport)) + tgSvcExport.tgSummary.Arn = aws.String("tg-svc-export-arn") + tgSvcExport.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeSvcExport)) + tgSvcExport.tags[model.K8SProtocolVersionKey] = aws.String("HTTP1") deleteTgs = append(deleteTgs, tgSvcExport) t.Run("Service Export does not exist", func(t *testing.T) { @@ -386,10 +379,11 @@ func Test_DeleteRoute_DeleteCases(t *testing.T) { var deleteTgs []tgListOutput tgSvc := copy(baseTg) - tgSvc.getTargetGroupOutput.Arn = aws.String("tg-svc-arn") - tgSvc.targetGroupTags.Tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeHTTPRoute)) - tgSvc.targetGroupTags.Tags[model.K8SRouteNameKey] = aws.String("route") - tgSvc.targetGroupTags.Tags[model.K8SRouteNamespaceKey] = aws.String("route-ns") + tgSvc.tgSummary.Arn = aws.String("tg-svc-arn") + tgSvc.tags[model.K8SSourceTypeKey] = aws.String(string(model.SourceTypeHTTPRoute)) + tgSvc.tags[model.K8SRouteNameKey] = aws.String("route") + tgSvc.tags[model.K8SRouteNamespaceKey] = aws.String("route-ns") + tgSvc.tags[model.K8SProtocolVersionKey] = aws.String("HTTP1") deleteTgs = append(deleteTgs, tgSvc) t.Run("Route does not exist", func(t *testing.T) { diff --git a/pkg/gateway/model_build_targetgroup.go b/pkg/gateway/model_build_targetgroup.go index 7c0450cb..e4373073 100644 --- a/pkg/gateway/model_build_targetgroup.go +++ b/pkg/gateway/model_build_targetgroup.go @@ -171,6 +171,7 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroup(ctx context.Contex spec.K8SClusterName = config.ClusterName spec.K8SServiceName = t.serviceExport.Name spec.K8SServiceNamespace = t.serviceExport.Namespace + spec.K8SProtocolVersion = protocolVersion stackTG, err := model.NewTargetGroup(t.stack, spec) if err != nil { @@ -361,6 +362,7 @@ func (t *backendRefTargetGroupModelBuildTask) buildTargetGroupSpec(ctx context.C spec.K8SServiceNamespace = backendRefNsName.Namespace spec.K8SRouteName = t.route.Name() spec.K8SRouteNamespace = t.route.Namespace() + spec.K8SProtocolVersion = protocolVersion return spec, nil } diff --git a/pkg/model/lattice/targetgroup.go b/pkg/model/lattice/targetgroup.go index 365a26f9..a79c6fc7 100644 --- a/pkg/model/lattice/targetgroup.go +++ b/pkg/model/lattice/targetgroup.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-application-networking-k8s/pkg/utils" "github.com/aws/aws-sdk-go/service/vpclattice" "math/rand" - "reflect" ) const ( @@ -18,6 +17,7 @@ const ( K8SRouteNameKey = aws.TagBase + "RouteName" K8SRouteNamespaceKey = aws.TagBase + "RouteNamespace" K8SSourceTypeKey = aws.TagBase + "SourceTypeKey" + K8SProtocolVersionKey = aws.TagBase + "ProtocolVersion" // Service specific tags K8SRouteTypeKey = aws.TagBase + "RouteType" @@ -51,6 +51,7 @@ type TargetGroupTagFields struct { K8SServiceNamespace string `json:"k8sservicenamespace"` K8SRouteName string `json:"k8sroutename"` K8SRouteNamespace string `json:"k8sroutenamespace"` + K8SProtocolVersion string `json:"k8sprotocolversion"` } type TargetGroupStatus struct { @@ -80,6 +81,20 @@ func TGTagFieldsFromTags(tags map[string]*string) TargetGroupTagFields { K8SServiceNamespace: getMapValue(tags, K8SServiceNamespaceKey), K8SRouteName: getMapValue(tags, K8SRouteNameKey), K8SRouteNamespace: getMapValue(tags, K8SRouteNamespaceKey), + K8SProtocolVersion: getMapValue(tags, K8SProtocolVersionKey), + } +} + +func TagsFromTGTagFields(tagFields TargetGroupTagFields) map[string]*string { + st := string(tagFields.K8SSourceType) + return map[string]*string{ + K8SClusterNameKey: &tagFields.K8SClusterName, + K8SRouteNameKey: &tagFields.K8SRouteName, + K8SRouteNamespaceKey: &tagFields.K8SRouteNamespace, + K8SServiceNameKey: &tagFields.K8SServiceName, + K8SServiceNamespaceKey: &tagFields.K8SServiceNamespace, + K8SSourceTypeKey: &st, + K8SProtocolVersionKey: &tagFields.K8SProtocolVersion, } } @@ -109,15 +124,7 @@ func GetParentRefType(s string) K8SSourceType { } func TagFieldsMatch(spec TargetGroupSpec, tags TargetGroupTagFields) bool { - specTags := TargetGroupTagFields{ - K8SClusterName: spec.K8SClusterName, - K8SSourceType: spec.K8SSourceType, - K8SServiceName: spec.K8SServiceName, - K8SServiceNamespace: spec.K8SServiceNamespace, - K8SRouteName: spec.K8SRouteName, - K8SRouteNamespace: spec.K8SRouteNamespace, - } - return reflect.DeepEqual(specTags, tags) + return spec.TargetGroupTagFields == tags } func NewTargetGroup(stack core.Stack, spec TargetGroupSpec) (*TargetGroup, error) { From 58c9091ebfba1cac11faf027f411d4ba945cc042 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Fri, 10 Nov 2023 17:45:44 -0800 Subject: [PATCH 2/3] Address comments --- controllers/route_controller_test.go | 2 +- pkg/aws/services/tagging.go | 25 +++++++++--------- pkg/aws/services/tagging_mocks.go | 26 +++++++++---------- pkg/deploy/lattice/target_group_manager.go | 19 +++++++------- .../lattice/target_group_manager_test.go | 20 +++++++------- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/controllers/route_controller_test.go b/controllers/route_controller_test.go index 17513312..07c9ca07 100644 --- a/controllers/route_controller_test.go +++ b/controllers/route_controller_test.go @@ -206,7 +206,7 @@ func TestRouteReconciler_ReconcileCreates(t *testing.T) { }, }, nil) // will trigger DNS Update - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return( []*vpclattice.TargetGroupSummary{}, nil).AnyTimes() // this will cause us to skip "unused delete" step mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return( diff --git a/pkg/aws/services/tagging.go b/pkg/aws/services/tagging.go index 52f0db63..6d22dbc9 100644 --- a/pkg/aws/services/tagging.go +++ b/pkg/aws/services/tagging.go @@ -11,10 +11,12 @@ import ( //go:generate mockgen -destination tagging_mocks.go -package services github.com/aws/aws-application-networking-k8s/pkg/aws/services Tagging +type ResourceType string + const ( resourceTypePrefix = "vpc-lattice:" - ResourceTypeTargetGroup = resourceTypePrefix + "targetgroup" + ResourceTypeTargetGroup ResourceType = resourceTypePrefix + "targetgroup" maxArnsPerGetResourcesApi = 100 ) @@ -25,18 +27,18 @@ type Tagging interface { taggingapiiface.ResourceGroupsTaggingAPIAPI // Receives a list of arns and returns arn-to-tags map. - GetTagsFromArns(ctx context.Context, arns []*string) (map[string]Tags, error) + GetTagsForArns(ctx context.Context, arns []string) (map[string]Tags, error) // Finds one resource that matches the given set of tags. - FindResourceWithTags(ctx context.Context, resourceType string, tags Tags) (*string, error) + FindResourcesByTags(ctx context.Context, resourceType ResourceType, tags Tags) ([]string, error) } type defaultTagging struct { taggingapiiface.ResourceGroupsTaggingAPIAPI } -func (t *defaultTagging) GetTagsFromArns(ctx context.Context, arns []*string) (map[string]Tags, error) { - chunks := utils.Chunks(arns, maxArnsPerGetResourcesApi) +func (t *defaultTagging) GetTagsForArns(ctx context.Context, arns []string) (map[string]Tags, error) { + chunks := utils.Chunks(utils.SliceMap(arns, aws.String), maxArnsPerGetResourcesApi) result := make(map[string]Tags) for _, chunk := range chunks { @@ -56,20 +58,19 @@ func (t *defaultTagging) GetTagsFromArns(ctx context.Context, arns []*string) (m return result, nil } -func (t *defaultTagging) FindResourceWithTags(ctx context.Context, resourceType string, tags Tags) (*string, error) { +func (t *defaultTagging) FindResourcesByTags(ctx context.Context, resourceType ResourceType, tags Tags) ([]string, error) { input := &taggingapi.GetResourcesInput{ TagFilters: convertTagsToFilter(tags), - ResourceTypeFilters: []*string{aws.String(resourceType)}, + ResourceTypeFilters: []*string{aws.String(string(resourceType))}, } resp, err := t.GetResourcesWithContext(ctx, input) if err != nil { return nil, err } - if len(resp.ResourceTagMappingList) == 0 { - return nil, NewNotFoundError("tag", "matching criteria") - } - // assume one result - return resp.ResourceTagMappingList[0].ResourceARN, nil + matchingArns := utils.SliceMap(resp.ResourceTagMappingList, func(t *taggingapi.ResourceTagMapping) string { + return aws.StringValue(t.ResourceARN) + }) + return matchingArns, nil } func NewDefaultTagging(sess *session.Session, region string) *defaultTagging { diff --git a/pkg/aws/services/tagging_mocks.go b/pkg/aws/services/tagging_mocks.go index 62a2672d..6f0d22ec 100644 --- a/pkg/aws/services/tagging_mocks.go +++ b/pkg/aws/services/tagging_mocks.go @@ -86,19 +86,19 @@ func (mr *MockTaggingMockRecorder) DescribeReportCreationWithContext(arg0, arg1 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeReportCreationWithContext", reflect.TypeOf((*MockTagging)(nil).DescribeReportCreationWithContext), varargs...) } -// FindResourceWithTags mocks base method. -func (m *MockTagging) FindResourceWithTags(arg0 context.Context, arg1 string, arg2 map[string]*string) (*string, error) { +// FindResourcesByTags mocks base method. +func (m *MockTagging) FindResourcesByTags(arg0 context.Context, arg1 ResourceType, arg2 map[string]*string) ([]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindResourceWithTags", arg0, arg1, arg2) - ret0, _ := ret[0].(*string) + ret := m.ctrl.Call(m, "FindResourcesByTags", arg0, arg1, arg2) + ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindResourceWithTags indicates an expected call of FindResourceWithTags. -func (mr *MockTaggingMockRecorder) FindResourceWithTags(arg0, arg1, arg2 interface{}) *gomock.Call { +// FindResourcesByTags indicates an expected call of FindResourcesByTags. +func (mr *MockTaggingMockRecorder) FindResourcesByTags(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindResourceWithTags", reflect.TypeOf((*MockTagging)(nil).FindResourceWithTags), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindResourcesByTags", reflect.TypeOf((*MockTagging)(nil).FindResourcesByTags), arg0, arg1, arg2) } // GetComplianceSummary mocks base method. @@ -433,19 +433,19 @@ func (mr *MockTaggingMockRecorder) GetTagValuesWithContext(arg0, arg1 interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagValuesWithContext", reflect.TypeOf((*MockTagging)(nil).GetTagValuesWithContext), varargs...) } -// GetTagsFromArns mocks base method. -func (m *MockTagging) GetTagsFromArns(arg0 context.Context, arg1 []*string) (map[string]map[string]*string, error) { +// GetTagsForArns mocks base method. +func (m *MockTagging) GetTagsForArns(arg0 context.Context, arg1 []string) (map[string]map[string]*string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTagsFromArns", arg0, arg1) + ret := m.ctrl.Call(m, "GetTagsForArns", arg0, arg1) ret0, _ := ret[0].(map[string]map[string]*string) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetTagsFromArns indicates an expected call of GetTagsFromArns. -func (mr *MockTaggingMockRecorder) GetTagsFromArns(arg0, arg1 interface{}) *gomock.Call { +// GetTagsForArns indicates an expected call of GetTagsForArns. +func (mr *MockTaggingMockRecorder) GetTagsForArns(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagsFromArns", reflect.TypeOf((*MockTagging)(nil).GetTagsFromArns), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTagsForArns", reflect.TypeOf((*MockTagging)(nil).GetTagsForArns), arg0, arg1) } // StartReportCreation mocks base method. diff --git a/pkg/deploy/lattice/target_group_manager.go b/pkg/deploy/lattice/target_group_manager.go index 2691eeaa..bbe16b9f 100644 --- a/pkg/deploy/lattice/target_group_manager.go +++ b/pkg/deploy/lattice/target_group_manager.go @@ -256,8 +256,10 @@ func (s *defaultTargetGroupManager) List(ctx context.Context) ([]tgListOutput, e if len(resp) == 0 { return nil, nil } - tgArns := utils.SliceMap(resp, func(tg *vpclattice.TargetGroupSummary) *string { return tg.Arn }) - tgArnToTagsMap, err := s.cloud.Tagging().GetTagsFromArns(ctx, tgArns) + tgArns := utils.SliceMap(resp, func(tg *vpclattice.TargetGroupSummary) string { + return aws.StringValue(tg.Arn) + }) + tgArnToTagsMap, err := s.cloud.Tagging().GetTagsForArns(ctx, tgArns) if err != nil { return nil, err @@ -275,23 +277,22 @@ func (s *defaultTargetGroupManager) findTargetGroup( ctx context.Context, modelTargetGroup *model.TargetGroup, ) (*vpclattice.GetTargetGroupOutput, error) { - arn, err := s.cloud.Tagging().FindResourceWithTags(ctx, services.ResourceTypeTargetGroup, + arns, err := s.cloud.Tagging().FindResourcesByTags(ctx, services.ResourceTypeTargetGroup, model.TagsFromTGTagFields(modelTargetGroup.Spec.TargetGroupTagFields)) if err != nil { return nil, err } - if arn == nil { + if len(arns) == 0 { return nil, nil } + // Can get only one arn through the above search criteria. + arn := arns[0] latticeTg, err := s.cloud.Lattice().GetTargetGroupWithContext(ctx, &vpclattice.GetTargetGroupInput{ - TargetGroupIdentifier: arn, + TargetGroupIdentifier: &arn, }) if err != nil { - if services.IsLatticeAPINotFoundErr(err) { - return nil, nil - } - return nil, err + return nil, services.IgnoreNotFound(err) } // we ignore create failed status, so may as well check for it first diff --git a/pkg/deploy/lattice/target_group_manager_test.go b/pkg/deploy/lattice/target_group_manager_test.go index c9b40766..80eee329 100644 --- a/pkg/deploy/lattice/target_group_manager_test.go +++ b/pkg/deploy/lattice/target_group_manager_test.go @@ -87,7 +87,7 @@ func Test_CreateTargetGroup_TGNotExist_Active(t *testing.T) { expectedTags[model.K8SRouteNamespaceKey] = &tgSpec.K8SRouteNamespace } - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).DoAndReturn( func(ctx context.Context, input *vpclattice.CreateTargetGroupInput, arg3 ...interface{}) (*vpclattice.CreateTargetGroupOutput, error) { assert.Equal(t, aws.Int64(int64(tgSpec.Port)), input.Config.Port) @@ -153,7 +153,7 @@ func Test_CreateTargetGroup_TGFailed_Active(t *testing.T) { Config: &vpclattice.TargetGroupConfig{}, } - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(&arn, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return([]string{arn}, nil) mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return(tgCreateOutput, nil) @@ -228,7 +228,7 @@ func Test_CreateTargetGroup_TGActive_UpdateHealthCheck(t *testing.T) { }, } - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(&arn, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return([]string{arn}, nil) mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) if tt.wantErr { @@ -292,7 +292,7 @@ func Test_CreateTargetGroup_ExistingTG_Status_Retry(t *testing.T) { }, } - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(&arn, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return([]string{arn}, nil) mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -341,7 +341,7 @@ func Test_CreateTargetGroup_NewTG_RetryStatus(t *testing.T) { Status: &tgStatus, } - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return(tgCreateOutput, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -370,7 +370,7 @@ func Test_Lattice_API_Errors(t *testing.T) { } // search error - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, errors.New("test")) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return(nil, errors.New("test")) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) _, err := tgManager.Upsert(ctx, &tgCreateInput) @@ -378,7 +378,7 @@ func Test_Lattice_API_Errors(t *testing.T) { assert.Equal(t, errors.New("test"), err) // create error - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) mockLattice.EXPECT().CreateTargetGroupWithContext(ctx, gomock.Any()).Return(nil, errors.New("test")) tgManager = NewTargetGroupManager(gwlog.FallbackLogger, cloud) @@ -507,7 +507,7 @@ func Test_DeleteTG_WithExistingTG(t *testing.T) { } var listTargetsOutput []*vpclattice.TargetSummary - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(tgOutput.Arn, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return([]string{*tgOutput.Arn}, nil) mockLattice.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(&tgOutput, nil) mockLattice.EXPECT().ListTargetsAsList(ctx, gomock.Any()).Return(listTargetsOutput, nil) @@ -540,7 +540,7 @@ func Test_DeleteTG_NothingToDelete(t *testing.T) { Status: nil, } - mockTagging.EXPECT().FindResourceWithTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) + mockTagging.EXPECT().FindResourcesByTags(ctx, gomock.Any(), gomock.Any()).Return(nil, nil) tgManager := NewTargetGroupManager(gwlog.FallbackLogger, cloud) err := tgManager.Delete(ctx, &tgDeleteInput) @@ -740,7 +740,7 @@ func Test_ListTG_TGsExist(t *testing.T) { // assume no tags mockTagging := mocks.NewMockTagging(c) - mockTagging.EXPECT().GetTagsFromArns(ctx, gomock.Any()).Return(nil, nil) + mockTagging.EXPECT().GetTagsForArns(ctx, gomock.Any()).Return(nil, nil) cloud := pkg_aws.NewDefaultCloudWithTagging(mockLattice, mockTagging, TestCloudConfig) From a2f7bb588118b95b3a6000c28317306d8e6693a1 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Tue, 14 Nov 2023 14:02:23 -0800 Subject: [PATCH 3/3] Address comments --- pkg/aws/services/tagging.go | 1 + pkg/deploy/lattice/target_group_manager.go | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/aws/services/tagging.go b/pkg/aws/services/tagging.go index 6d22dbc9..cb52498b 100644 --- a/pkg/aws/services/tagging.go +++ b/pkg/aws/services/tagging.go @@ -18,6 +18,7 @@ const ( ResourceTypeTargetGroup ResourceType = resourceTypePrefix + "targetgroup" + // https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_GetResources.html#API_GetResources_RequestSyntax maxArnsPerGetResourcesApi = 100 ) diff --git a/pkg/deploy/lattice/target_group_manager.go b/pkg/deploy/lattice/target_group_manager.go index bbe16b9f..1ba0fe60 100644 --- a/pkg/deploy/lattice/target_group_manager.go +++ b/pkg/deploy/lattice/target_group_manager.go @@ -128,7 +128,7 @@ func (s *defaultTargetGroupManager) update(ctx context.Context, targetGroup *mod if err != nil { return model.TargetGroupStatus{}, - fmt.Errorf("Failed UpdateTargetGroup %s due to %s", aws.StringValue(latticeTg.Id), err) + fmt.Errorf("Failed UpdateTargetGroup %s due to %w", aws.StringValue(latticeTg.Id), err) } s.log.Infof("Success UpdateTargetGroup %s", aws.StringValue(latticeTg.Id)) @@ -285,7 +285,11 @@ func (s *defaultTargetGroupManager) findTargetGroup( if len(arns) == 0 { return nil, nil } - // Can get only one arn through the above search criteria. + // Tag fields guarantee one result, as there can be only one target group for one service/route combination. + // We move forward but log this situation to help troubleshooting + if len(arns) > 1 { + s.log.Warnw("Target groups with conflicting tags found", "arns", arns) + } arn := arns[0] latticeTg, err := s.cloud.Lattice().GetTargetGroupWithContext(ctx, &vpclattice.GetTargetGroupInput{