From 839a1185cfcd25a993f7fccd54ab77c971c71fd2 Mon Sep 17 00:00:00 2001 From: Peter Rifel Date: Thu, 29 Oct 2020 17:22:04 -0500 Subject: [PATCH] Create cloudmock implementations for elbv2 API calls used by future NLB support --- cloudmock/aws/mockelbv2/BUILD.bazel | 9 +- cloudmock/aws/mockelbv2/api.go | 106 ++----------- cloudmock/aws/mockelbv2/listeners.go | 118 +++++++++++++++ cloudmock/aws/mockelbv2/loadbalancers.go | 185 +++++++++++++++++++++++ cloudmock/aws/mockelbv2/tags.go | 77 ++++++++++ cloudmock/aws/mockelbv2/targetgroups.go | 126 +++++++++++++++ 6 files changed, 523 insertions(+), 98 deletions(-) create mode 100644 cloudmock/aws/mockelbv2/listeners.go create mode 100644 cloudmock/aws/mockelbv2/loadbalancers.go create mode 100644 cloudmock/aws/mockelbv2/tags.go create mode 100644 cloudmock/aws/mockelbv2/targetgroups.go diff --git a/cloudmock/aws/mockelbv2/BUILD.bazel b/cloudmock/aws/mockelbv2/BUILD.bazel index 6a13758f4f..172fe1cdad 100644 --- a/cloudmock/aws/mockelbv2/BUILD.bazel +++ b/cloudmock/aws/mockelbv2/BUILD.bazel @@ -2,11 +2,18 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["api.go"], + srcs = [ + "api.go", + "listeners.go", + "loadbalancers.go", + "tags.go", + "targetgroups.go", + ], importpath = "k8s.io/kops/cloudmock/aws/mockelbv2", visibility = ["//visibility:public"], deps = [ "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/aws/awserr:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/elbv2:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/elbv2/elbv2iface:go_default_library", "//vendor/k8s.io/klog/v2:go_default_library", diff --git a/cloudmock/aws/mockelbv2/api.go b/cloudmock/aws/mockelbv2/api.go index afb79e8022..9f6d0cb029 100644 --- a/cloudmock/aws/mockelbv2/api.go +++ b/cloudmock/aws/mockelbv2/api.go @@ -19,10 +19,8 @@ package mockelbv2 import ( "sync" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elbv2" "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface" - "k8s.io/klog/v2" ) type MockELBV2 struct { @@ -31,7 +29,14 @@ type MockELBV2 struct { mutex sync.Mutex LoadBalancers map[string]*loadBalancer + lbCount int TargetGroups map[string]*targetGroup + tgCount int + Listeners map[string]*listener + listenerCount int + LBAttributes map[string][]*elbv2.LoadBalancerAttribute + + Tags map[string]*elbv2.TagDescription } type loadBalancer struct { @@ -42,99 +47,6 @@ type targetGroup struct { description elbv2.TargetGroup } -func (m *MockELBV2) DescribeLoadBalancers(request *elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - klog.V(2).Infof("DescribeLoadBalancers v2 %v", request) - - if request.PageSize != nil { - klog.Warningf("PageSize not implemented") - } - if request.Marker != nil { - klog.Fatalf("Marker not implemented") - } - - var elbs []*elbv2.LoadBalancer - for _, elb := range m.LoadBalancers { - match := false - - if len(request.LoadBalancerArns) > 0 { - for _, name := range request.LoadBalancerArns { - if aws.StringValue(elb.description.LoadBalancerArn) == aws.StringValue(name) { - match = true - } - } - } else { - match = true - } - - if match { - elbs = append(elbs, &elb.description) - } - } - - return &elbv2.DescribeLoadBalancersOutput{ - LoadBalancers: elbs, - }, nil -} - -func (m *MockELBV2) DescribeLoadBalancersPages(request *elbv2.DescribeLoadBalancersInput, callback func(p *elbv2.DescribeLoadBalancersOutput, lastPage bool) (shouldContinue bool)) error { - // For the mock, we just send everything in one page - page, err := m.DescribeLoadBalancers(request) - if err != nil { - return err - } - - callback(page, false) - - return nil -} - -func (m *MockELBV2) DescribeTargetGroups(request *elbv2.DescribeTargetGroupsInput) (*elbv2.DescribeTargetGroupsOutput, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - klog.V(2).Infof("DescribeTargetGroups %v", request) - - if request.PageSize != nil { - klog.Warningf("PageSize not implemented") - } - if request.Marker != nil { - klog.Fatalf("Marker not implemented") - } - - var tgs []*elbv2.TargetGroup - for _, tg := range m.TargetGroups { - match := false - - if len(request.TargetGroupArns) > 0 { - for _, name := range request.TargetGroupArns { - if aws.StringValue(tg.description.TargetGroupArn) == aws.StringValue(name) { - match = true - } - } - } else { - match = true - } - - if match { - tgs = append(tgs, &tg.description) - } - } - - return &elbv2.DescribeTargetGroupsOutput{ - TargetGroups: tgs, - }, nil -} - -func (m *MockELBV2) DescribeTargetGroupsPages(request *elbv2.DescribeTargetGroupsInput, callback func(p *elbv2.DescribeTargetGroupsOutput, lastPage bool) (shouldContinue bool)) error { - page, err := m.DescribeTargetGroups(request) - if err != nil { - return err - } - - callback(page, false) - - return nil +type listener struct { + description elbv2.Listener } diff --git a/cloudmock/aws/mockelbv2/listeners.go b/cloudmock/aws/mockelbv2/listeners.go new file mode 100644 index 0000000000..b8937fae8d --- /dev/null +++ b/cloudmock/aws/mockelbv2/listeners.go @@ -0,0 +1,118 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mockelbv2 + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "k8s.io/klog/v2" +) + +func (m *MockELBV2) DescribeListeners(request *elbv2.DescribeListenersInput) (*elbv2.DescribeListenersOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DescribeListeners v2 %v", request) + + resp := &elbv2.DescribeListenersOutput{ + Listeners: make([]*elbv2.Listener, 0), + } + for _, l := range m.Listeners { + listener := l.description + if aws.StringValue(request.LoadBalancerArn) == aws.StringValue(listener.LoadBalancerArn) { + resp.Listeners = append(resp.Listeners, &listener) + } else { + for _, reqARN := range request.ListenerArns { + if aws.StringValue(reqARN) == aws.StringValue(listener.ListenerArn) { + resp.Listeners = append(resp.Listeners, &listener) + } + } + } + } + return resp, nil +} + +func (m *MockELBV2) CreateListener(request *elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("CreateListener v2 %v", request) + + l := elbv2.Listener{ + DefaultActions: request.DefaultActions, + LoadBalancerArn: request.LoadBalancerArn, + Port: request.Port, + Certificates: request.Certificates, + Protocol: request.Protocol, + SslPolicy: request.SslPolicy, + } + + lbARN := aws.StringValue(request.LoadBalancerArn) + if _, ok := m.LoadBalancers[lbARN]; !ok { + return nil, fmt.Errorf("LoadBalancerArn not found %v", aws.StringValue(request.LoadBalancerArn)) + } + + m.listenerCount++ + arn := fmt.Sprintf("%v/%v", strings.Replace(lbARN, ":loadbalancer/", ":listener/", 1), m.listenerCount) + l.ListenerArn = aws.String(arn) + + if m.Listeners == nil { + m.Listeners = make(map[string]*listener) + } + + tgARN := aws.StringValue(l.DefaultActions[0].TargetGroupArn) + + if _, ok := m.TargetGroups[tgARN]; ok { + found := false + for _, lb := range m.TargetGroups[tgARN].description.LoadBalancerArns { + if aws.StringValue(lb) == lbARN { + found = true + break + } + } + if !found { + m.TargetGroups[tgARN].description.LoadBalancerArns = append(m.TargetGroups[tgARN].description.LoadBalancerArns, aws.String(lbARN)) + } + } + + m.Listeners[arn] = &listener{description: l} + return &elbv2.CreateListenerOutput{Listeners: []*elbv2.Listener{&l}}, nil +} + +func (m *MockELBV2) DeleteListener(request *elbv2.DeleteListenerInput) (*elbv2.DeleteListenerOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DeleteListener v2 %v", request) + + lARN := aws.StringValue(request.ListenerArn) + if _, ok := m.Listeners[lARN]; !ok { + return nil, fmt.Errorf("Listener not found %v", lARN) + } + delete(m.Listeners, lARN) + return nil, nil +} + +func (m *MockELBV2) ModifyListener(request *elbv2.ModifyListenerInput) (*elbv2.ModifyListenerOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + klog.Fatalf("elbv2.ModifyListener() not implemented") + return nil, nil +} diff --git a/cloudmock/aws/mockelbv2/loadbalancers.go b/cloudmock/aws/mockelbv2/loadbalancers.go new file mode 100644 index 0000000000..52daada518 --- /dev/null +++ b/cloudmock/aws/mockelbv2/loadbalancers.go @@ -0,0 +1,185 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mockelbv2 + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "k8s.io/klog/v2" +) + +func (m *MockELBV2) DescribeLoadBalancers(request *elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DescribeLoadBalancers v2 %v", request) + + if request.PageSize != nil { + klog.Warningf("PageSize not implemented") + } + if request.Marker != nil { + klog.Fatalf("Marker not implemented") + } + + var elbs []*elbv2.LoadBalancer + for _, elb := range m.LoadBalancers { + match := false + + if len(request.LoadBalancerArns) > 0 { + for _, name := range request.LoadBalancerArns { + if aws.StringValue(elb.description.LoadBalancerArn) == aws.StringValue(name) { + match = true + } + } + } else { + match = true + } + + if match { + elbs = append(elbs, &elb.description) + } + } + + return &elbv2.DescribeLoadBalancersOutput{ + LoadBalancers: elbs, + }, nil +} + +func (m *MockELBV2) DescribeLoadBalancersPages(request *elbv2.DescribeLoadBalancersInput, callback func(p *elbv2.DescribeLoadBalancersOutput, lastPage bool) (shouldContinue bool)) error { + // For the mock, we just send everything in one page + page, err := m.DescribeLoadBalancers(request) + if err != nil { + return err + } + + callback(page, false) + + return nil +} + +func (m *MockELBV2) CreateLoadBalancer(request *elbv2.CreateLoadBalancerInput) (*elbv2.CreateLoadBalancerOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("CreateLoadBalancer v2 %v", request) + + lb := elbv2.LoadBalancer{ + LoadBalancerName: request.Name, + Scheme: request.Scheme, + Type: request.Type, + DNSName: aws.String(fmt.Sprintf("%v.amazonaws.com", aws.StringValue(request.Name))), + CanonicalHostedZoneId: aws.String("HZ123456"), + } + zones := make([]*elbv2.AvailabilityZone, 0) + for _, subnet := range request.Subnets { + zones = append(zones, &elbv2.AvailabilityZone{ + SubnetId: subnet, + }) + } + lb.AvailabilityZones = zones + + // This is hardcoded because AWS derives it from the subnets above + // But we don'y rely on the NLB's VPC ID at all in awstasks + lb.VpcId = aws.String("vpc-1") + + m.lbCount++ + arn := fmt.Sprintf("arn:aws:elasticloadbalancing:us-test-1:000000000000:loadbalancer/net/%v/%v", aws.StringValue(request.Name), m.lbCount) + + lb.LoadBalancerArn = aws.String(arn) + + if m.LoadBalancers == nil { + m.LoadBalancers = make(map[string]*loadBalancer) + } + if m.LBAttributes == nil { + m.LBAttributes = make(map[string][]*elbv2.LoadBalancerAttribute) + } + + m.LoadBalancers[arn] = &loadBalancer{description: lb} + m.LBAttributes[arn] = make([]*elbv2.LoadBalancerAttribute, 0) + + return &elbv2.CreateLoadBalancerOutput{LoadBalancers: []*elbv2.LoadBalancer{&lb}}, nil +} + +func (m *MockELBV2) DescribeLoadBalancerAttributes(request *elbv2.DescribeLoadBalancerAttributesInput) (*elbv2.DescribeLoadBalancerAttributesOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DescribeLoadBalancerAttributes v2 %v", request) + + if attr, ok := m.LBAttributes[aws.StringValue(request.LoadBalancerArn)]; ok { + return &elbv2.DescribeLoadBalancerAttributesOutput{ + Attributes: attr, + }, nil + } + return nil, fmt.Errorf("LoadBalancerNotFound: %v", aws.StringValue(request.LoadBalancerArn)) +} + +func (m *MockELBV2) ModifyLoadBalancerAttributes(request *elbv2.ModifyLoadBalancerAttributesInput) (*elbv2.ModifyLoadBalancerAttributesOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("ModifyLoadBalancerAttributes v2 %v", request) + + if m.LBAttributes == nil { + m.LBAttributes = make(map[string][]*elbv2.LoadBalancerAttribute) + } + + arn := aws.StringValue(request.LoadBalancerArn) + if _, ok := m.LBAttributes[arn]; ok { + for _, reqAttr := range request.Attributes { + found := false + for _, lbAttr := range m.LBAttributes[arn] { + if aws.StringValue(reqAttr.Key) == aws.StringValue(lbAttr.Key) { + lbAttr.Value = reqAttr.Value + found = true + } + } + if !found { + m.LBAttributes[arn] = append(m.LBAttributes[arn], reqAttr) + } + } + return &elbv2.ModifyLoadBalancerAttributesOutput{ + Attributes: m.LBAttributes[arn], + }, nil + } + return nil, fmt.Errorf("LoadBalancerNotFound: %v", aws.StringValue(request.LoadBalancerArn)) +} + +func (m *MockELBV2) SetSubnets(request *elbv2.SetSubnetsInput) (*elbv2.SetSubnetsOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + klog.Fatalf("elbv2.SetSubnets() not implemented") + return nil, nil +} + +func (m *MockELBV2) DeleteLoadBalancer(request *elbv2.DeleteLoadBalancerInput) (*elbv2.DeleteLoadBalancerOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DeleteLoadBalancer %v", request) + + arn := aws.StringValue(request.LoadBalancerArn) + delete(m.LoadBalancers, arn) + for listenerARN, listener := range m.Listeners { + if aws.StringValue(listener.description.LoadBalancerArn) == arn { + delete(m.Listeners, listenerARN) + } + } + return &elbv2.DeleteLoadBalancerOutput{}, nil +} diff --git a/cloudmock/aws/mockelbv2/tags.go b/cloudmock/aws/mockelbv2/tags.go new file mode 100644 index 0000000000..636fe8a958 --- /dev/null +++ b/cloudmock/aws/mockelbv2/tags.go @@ -0,0 +1,77 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mockelbv2 + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "k8s.io/klog/v2" +) + +func (m *MockELBV2) AddTags(request *elbv2.AddTagsInput) (*elbv2.AddTagsOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("AddTags v2 %v", request) + + if m.Tags == nil { + m.Tags = make(map[string]*elbv2.TagDescription) + } + + for _, reqARN := range request.ResourceArns { + arn := aws.StringValue(reqARN) + if t, ok := m.Tags[arn]; ok { + for _, reqTag := range request.Tags { + found := false + for _, tag := range t.Tags { + if aws.StringValue(reqTag.Key) == aws.StringValue(tag.Key) { + tag.Value = reqTag.Value + } + } + if !found { + m.Tags[arn].Tags = append(m.Tags[arn].Tags, reqTag) + } + } + } else { + m.Tags[arn] = &elbv2.TagDescription{ + ResourceArn: reqARN, + Tags: request.Tags, + } + } + } + + return &elbv2.AddTagsOutput{}, nil +} + +func (m *MockELBV2) DescribeTags(request *elbv2.DescribeTagsInput) (*elbv2.DescribeTagsOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DescribeTags v2 %v", request) + + resp := &elbv2.DescribeTagsOutput{ + TagDescriptions: make([]*elbv2.TagDescription, 0), + } + for tagARN, tagDesc := range m.Tags { + for _, reqARN := range request.ResourceArns { + if tagARN == aws.StringValue(reqARN) { + resp.TagDescriptions = append(resp.TagDescriptions, tagDesc) + } + } + } + return resp, nil +} diff --git a/cloudmock/aws/mockelbv2/targetgroups.go b/cloudmock/aws/mockelbv2/targetgroups.go new file mode 100644 index 0000000000..e11ec297f2 --- /dev/null +++ b/cloudmock/aws/mockelbv2/targetgroups.go @@ -0,0 +1,126 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mockelbv2 + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/elbv2" + "k8s.io/klog/v2" +) + +func (m *MockELBV2) DescribeTargetGroups(request *elbv2.DescribeTargetGroupsInput) (*elbv2.DescribeTargetGroupsOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DescribeTargetGroups %v", request) + + if request.PageSize != nil { + klog.Warningf("PageSize not implemented") + } + if request.Marker != nil { + klog.Fatalf("Marker not implemented") + } + + var tgs []*elbv2.TargetGroup + for _, tg := range m.TargetGroups { + match := false + + if len(request.TargetGroupArns) > 0 { + for _, name := range request.TargetGroupArns { + if aws.StringValue(tg.description.TargetGroupArn) == aws.StringValue(name) { + match = true + } + } + } else if request.LoadBalancerArn != nil { + if len(tg.description.LoadBalancerArns) > 0 && aws.StringValue(tg.description.LoadBalancerArns[0]) == aws.StringValue(request.LoadBalancerArn) { + match = true + } + } else if len(request.Names) > 0 { + for _, name := range request.Names { + if aws.StringValue(tg.description.TargetGroupName) == aws.StringValue(name) { + match = true + } + } + } else { + match = true + } + + if match { + tgs = append(tgs, &tg.description) + } + } + + if len(tgs) == 0 && len(request.TargetGroupArns) > 0 || request.LoadBalancerArn != nil { + return nil, awserr.New(elbv2.ErrCodeTargetGroupNotFoundException, "target group not found", nil) + } + + return &elbv2.DescribeTargetGroupsOutput{ + TargetGroups: tgs, + }, nil +} + +func (m *MockELBV2) DescribeTargetGroupsPages(request *elbv2.DescribeTargetGroupsInput, callback func(p *elbv2.DescribeTargetGroupsOutput, lastPage bool) (shouldContinue bool)) error { + page, err := m.DescribeTargetGroups(request) + if err != nil { + return err + } + + callback(page, false) + + return nil +} + +func (m *MockELBV2) CreateTargetGroup(request *elbv2.CreateTargetGroupInput) (*elbv2.CreateTargetGroupOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("CreateTargetGroup %v", request) + + tg := elbv2.TargetGroup{ + TargetGroupName: request.Name, + Port: request.Port, + Protocol: request.Protocol, + VpcId: request.VpcId, + HealthyThresholdCount: request.HealthyThresholdCount, + UnhealthyThresholdCount: request.UnhealthyThresholdCount, + } + + m.tgCount++ + arn := fmt.Sprintf("arn:aws:elasticloadbalancing:us-test-1:000000000000:targetgroup/%v/%v", aws.StringValue(request.Name), m.tgCount) + tg.TargetGroupArn = aws.String(arn) + + if m.TargetGroups == nil { + m.TargetGroups = make(map[string]*targetGroup) + } + + m.TargetGroups[arn] = &targetGroup{description: tg} + return &elbv2.CreateTargetGroupOutput{TargetGroups: []*elbv2.TargetGroup{&tg}}, nil +} + +func (m *MockELBV2) DeleteTargetGroup(request *elbv2.DeleteTargetGroupInput) (*elbv2.DeleteTargetGroupOutput, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + klog.Infof("DeleteTargetGroup %v", request) + + arn := aws.StringValue(request.TargetGroupArn) + delete(m.TargetGroups, arn) + return &elbv2.DeleteTargetGroupOutput{}, nil +}