diff --git a/api/cloud-control/v1beta1/vpcpeering_builder.go b/api/cloud-control/v1beta1/vpcpeering_builder.go index d314932ca..4d2326857 100644 --- a/api/cloud-control/v1beta1/vpcpeering_builder.go +++ b/api/cloud-control/v1beta1/vpcpeering_builder.go @@ -113,6 +113,11 @@ func (b *VpcPeeringBuilder) WithLocalPeeringName(localPeeringName string) *VpcPe return b } +func (b *VpcPeeringBuilder) WithRemoteRouteTableUpdateStrategy(strategy AwsRouteTableUpdateStrategy) *VpcPeeringBuilder { + b.Obj.Spec.Details.RemoteRouteTableUpdateStrategy = strategy + return b +} + func (b *VpcPeeringBuilder) Build() *VpcPeering { return &b.Obj } diff --git a/api/cloud-control/v1beta1/vpcpeering_types.go b/api/cloud-control/v1beta1/vpcpeering_types.go index a52f3ebab..a3a52e1f5 100644 --- a/api/cloud-control/v1beta1/vpcpeering_types.go +++ b/api/cloud-control/v1beta1/vpcpeering_types.go @@ -43,6 +43,15 @@ const ( VpcPeeringRemoteNetworkField = ".spec.details.remoteNetwork" ) +type AwsRouteTableUpdateStrategy string + +const ( + AwsRouteTableUpdateStrategyAuto AwsRouteTableUpdateStrategy = "AUTO" + AwsRouteTableUpdateStrategyNone AwsRouteTableUpdateStrategy = "NONE" + AwsRouteTableUpdateStrategyMatched AwsRouteTableUpdateStrategy = "MATCHED" + AwsRouteTableUpdateStrategyUnmatched AwsRouteTableUpdateStrategy = "UNMATCHED" +) + // VpcPeeringSpec defines the desired state of VpcPeering // +kubebuilder:validation:XValidation:rule=(has(self.vpcPeering) && !has(self.details) || !has(self.vpcPeering) && has(self.details)), message="Only one of details or vpcPeering can be specified." type VpcPeeringSpec struct { @@ -76,6 +85,10 @@ type VpcPeeringDetails struct { ImportCustomRoutes bool `json:"importCustomRoutes,omitempty"` DeleteRemotePeering bool `json:"deleteRemotePeering,omitempty"` + + // +kubebuilder:default:=AUTO + // +kubebuilder:validation:Enum=AUTO;NONE;MATCHED;UNMATCHED + RemoteRouteTableUpdateStrategy AwsRouteTableUpdateStrategy `json:"remoteRouteTableUpdateStrategy,omitempty"` } // +kubebuilder:validation:MinProperties=1 diff --git a/api/cloud-resources/v1beta1/awsvpcpeering_types.go b/api/cloud-resources/v1beta1/awsvpcpeering_types.go index 53d4267c2..be1a57e59 100644 --- a/api/cloud-resources/v1beta1/awsvpcpeering_types.go +++ b/api/cloud-resources/v1beta1/awsvpcpeering_types.go @@ -21,6 +21,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type AwsRouteTableUpdateStrategy string + +const ( + AwsRouteTableUpdateStrategyAuto AwsRouteTableUpdateStrategy = "AUTO" + AwsRouteTableUpdateStrategyNone AwsRouteTableUpdateStrategy = "NONE" + AwsRouteTableUpdateStrategyMatched AwsRouteTableUpdateStrategy = "MATCHED" + AwsRouteTableUpdateStrategyUnmatched AwsRouteTableUpdateStrategy = "UNMATCHED" +) + // AwsVpcPeeringSpec defines the desired state of AwsVpcPeering type AwsVpcPeeringSpec struct { @@ -37,6 +46,11 @@ type AwsVpcPeeringSpec struct { RemoteAccountId string `json:"remoteAccountId"` DeleteRemotePeering bool `json:"deleteRemotePeering,omitempty"` + + // +kubebuilder:default:=AUTO + // +kubebuilder:validation:Enum=AUTO;NONE;MATCHED;UNMATCHED + // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemoteRouteTableUpdateStrategy is immutable." + RemoteRouteTableUpdateStrategy AwsRouteTableUpdateStrategy `json:"remoteRouteTableUpdateStrategy,omitempty"` } // AwsVpcPeeringStatus defines the observed state of AwsVpcPeering diff --git a/config/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml b/config/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml index a6393d025..aca11ca0d 100644 --- a/config/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml +++ b/config/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml @@ -82,6 +82,14 @@ spec: x-kubernetes-validations: - message: Remote network name is required. rule: (self.name != "") + remoteRouteTableUpdateStrategy: + default: AUTO + enum: + - AUTO + - NONE + - MATCHED + - UNMATCHED + type: string required: - localNetwork - remoteNetwork diff --git a/config/crd/bases/cloud-resources.kyma-project.io_awsvpcpeerings.yaml b/config/crd/bases/cloud-resources.kyma-project.io_awsvpcpeerings.yaml index 96af69557..ec8a956b7 100644 --- a/config/crd/bases/cloud-resources.kyma-project.io_awsvpcpeerings.yaml +++ b/config/crd/bases/cloud-resources.kyma-project.io_awsvpcpeerings.yaml @@ -4,7 +4,7 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.16.5 - cloud-resources.kyma-project.io/version: v0.0.2 + cloud-resources.kyma-project.io/version: v0.0.3 name: awsvpcpeerings.cloud-resources.kyma-project.io spec: group: cloud-resources.kyma-project.io @@ -58,6 +58,17 @@ spec: x-kubernetes-validations: - message: RemoteRegion is immutable. rule: (self == oldSelf) + remoteRouteTableUpdateStrategy: + default: AUTO + enum: + - AUTO + - NONE + - MATCHED + - UNMATCHED + type: string + x-kubernetes-validations: + - message: RemoteRouteTableUpdateStrategy is immutable. + rule: (self == oldSelf) remoteVpcId: type: string x-kubernetes-validations: diff --git a/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml b/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml index a6393d025..aca11ca0d 100644 --- a/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml +++ b/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml @@ -82,6 +82,14 @@ spec: x-kubernetes-validations: - message: Remote network name is required. rule: (self.name != "") + remoteRouteTableUpdateStrategy: + default: AUTO + enum: + - AUTO + - NONE + - MATCHED + - UNMATCHED + type: string required: - localNetwork - remoteNetwork diff --git a/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsvpcpeerings.yaml b/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsvpcpeerings.yaml index 96af69557..ec8a956b7 100644 --- a/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsvpcpeerings.yaml +++ b/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsvpcpeerings.yaml @@ -4,7 +4,7 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.16.5 - cloud-resources.kyma-project.io/version: v0.0.2 + cloud-resources.kyma-project.io/version: v0.0.3 name: awsvpcpeerings.cloud-resources.kyma-project.io spec: group: cloud-resources.kyma-project.io @@ -58,6 +58,17 @@ spec: x-kubernetes-validations: - message: RemoteRegion is immutable. rule: (self == oldSelf) + remoteRouteTableUpdateStrategy: + default: AUTO + enum: + - AUTO + - NONE + - MATCHED + - UNMATCHED + type: string + x-kubernetes-validations: + - message: RemoteRouteTableUpdateStrategy is immutable. + rule: (self == oldSelf) remoteVpcId: type: string x-kubernetes-validations: diff --git a/config/patchAfterMakeManifests.sh b/config/patchAfterMakeManifests.sh index dcd451d14..d6b69498f 100755 --- a/config/patchAfterMakeManifests.sh +++ b/config/patchAfterMakeManifests.sh @@ -17,6 +17,6 @@ yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.4 yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.3"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_gcpnfsvolumerestores.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.4"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_gcpnfsbackupschedules.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.3"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml -yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.2"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsvpcpeerings.yaml +yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.3"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsvpcpeerings.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.1"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_cloudresources.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.3"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsnfsbackupschedules.yaml diff --git a/internal/controller/cloud-control/vpcpeering_aws_test.go b/internal/controller/cloud-control/vpcpeering_aws_test.go index def75a87f..9aa8f9eb5 100644 --- a/internal/controller/cloud-control/vpcpeering_aws_test.go +++ b/internal/controller/cloud-control/vpcpeering_aws_test.go @@ -290,36 +290,36 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all local route tables has one new route with destination CIDR matching remote VPC CIDR", func() { - Expect(awsMockLocal.GetRouteCount(infra.Ctx(), localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRouteCount(localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). To(Equal(2)) - Expect(awsMockLocal.GetRoute(infra.Ctx(), localVpcId, localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRoute(localVpcId, localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). NotTo(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) - Expect(awsMockLocal.GetRoute(infra.Ctx(), localVpcId, localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRoute(localVpcId, localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). ToNot(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) - Expect(awsMockLocal.GetRoute(infra.Ctx(), wrong2VpcId, wrong2RouteTable, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRoute(wrong2VpcId, wrong2RouteTable, kcpPeering.Status.Id, remoteVpcCidr)). To(BeNil(), fmt.Sprintf("Route table %s should not have route with target %s and destination %s", wrong2RouteTable, kcpPeering.Status.Id, remoteVpcCidr)) }) By("And Then all remote route tables has one new route with destination CIDR matching VPC CIDR", func() { - Expect(awsMockRemote.GetRouteCount(infra.Ctx(), remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRouteCount(remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). To(Equal(2)) - Expect(awsMockRemote.GetRoute(infra.Ctx(), remoteVpcId, remoteMainRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRoute(remoteVpcId, remoteMainRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). NotTo(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) - Expect(awsMockRemote.GetRoute(infra.Ctx(), remoteVpcId, remoteRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRoute(remoteVpcId, remoteRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). ToNot(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) - Expect(awsMockRemote.GetRoute(infra.Ctx(), wrong3VpcId, wrong3RouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRoute(wrong3VpcId, wrong3RouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). To(BeNil(), fmt.Sprintf("Route table %s should not be modified", wrong2RouteTable)) }) @@ -343,7 +343,7 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all local route tables has no routes with destination CIDR matching remote VPC CIDR", func() { - Expect(awsMockLocal.GetRouteCount(infra.Ctx(), localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRouteCount(localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). To(Equal(0)) }) @@ -353,7 +353,7 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all remote route tables has no routes with destination CIDR matching local VPC CIDR", func() { - Expect(awsMockRemote.GetRouteCount(infra.Ctx(), remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRouteCount(remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). To(Equal(0)) }) }) @@ -562,27 +562,27 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all local route tables has one new route with destination CIDR matching remote VPC CIDR", func() { - Expect(awsMockLocal.GetRouteCount(infra.Ctx(), localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRouteCount(localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). To(Equal(2)) - Expect(awsMockLocal.GetRoute(infra.Ctx(), localVpcId, localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRoute(localVpcId, localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). NotTo(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) - Expect(awsMockLocal.GetRoute(infra.Ctx(), localVpcId, localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRoute(localVpcId, localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)). ToNot(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) }) By("And Then all remote route tables has one new route with destination CIDR matching VPC CIDR", func() { - Expect(awsMockRemote.GetRouteCount(infra.Ctx(), remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRouteCount(remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). To(Equal(2)) - Expect(awsMockRemote.GetRoute(infra.Ctx(), remoteVpcId, remoteMainRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRoute(remoteVpcId, remoteMainRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). NotTo(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localMainRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) - Expect(awsMockRemote.GetRoute(infra.Ctx(), remoteVpcId, remoteRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRoute(remoteVpcId, remoteRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). ToNot(BeNil(), fmt.Sprintf("Route table %s should have route with target %s and destination %s", localRouteTable, kcpPeering.Status.Id, remoteVpcCidr)) }) @@ -633,7 +633,7 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all local route tables has no routes with destination CIDR matching remote VPC CIDR", func() { - Expect(awsMockLocal.GetRouteCount(infra.Ctx(), localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). + Expect(awsMockLocal.GetRouteCount(localVpcId, kcpPeering.Status.Id, remoteVpcCidr)). To(Equal(0)) }) @@ -644,7 +644,7 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all remote route tables has routes with destination CIDR matching local VPC CIDR", func() { - Expect(awsMockRemote.GetRouteCount(infra.Ctx(), remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRouteCount(remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). To(Equal(2)) }) }) @@ -874,8 +874,250 @@ var _ = Describe("Feature: KCP VpcPeering", func() { }) By("And Then all remote route tables has routes with destination CIDR matching local VPC CIDR", func() { - Expect(awsMockRemote.GetRouteCount(infra.Ctx(), remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). + Expect(awsMockRemote.GetRouteCount(remoteVpcId, kcpPeering.Status.RemoteId, localVpcCidr)). To(Equal(2)) }) }) + + It("Scenario: KCP AWS VpcPeering remote route table update strategy MATCHED", func() { + const ( + kymaName = "612573aa-58be-4670-b7b2-ca4c60fb8b99" + kcpPeeringName = "0cb1ecb4-de6d-4146-93c7-df2a71e6f83e" + localVpcId = "vpc-5b6b20ad2b85f945b" + localVpcCidr = "10.180.0.0/16" + remoteVpcId = "vpc-9c7b1757ffb17f3db" + remoteVpcCidr = "10.200.0.0/16" + remoteAccountId = "777755557777" + remoteRegion = "eu-west1" + localMainRouteTable = "rtb-7ce283587d14d4517" + localRouteTable = "rtb-1c690daffb668e1cc" + remoteMainRouteTable = "rtb-d1605c3e2153551ee" + remoteRouteTable = "rtb-e03bfb82225944cdf" + remoteRouteTableTagged = "rtb-04214f38752ba4e85" + ) + + scope := &cloudcontrolv1beta1.Scope{} + + By("Given Scope exists", func() { + // Tell Scope reconciler to ignore this kymaName + scopePkg.Ignore.AddName(kymaName) + + Eventually(CreateScopeAws). + WithArguments(infra.Ctx(), infra, scope, WithName(kymaName)). + Should(Succeed()) + }) + + vpcName := scope.Spec.Scope.Aws.VpcNetwork + remoteVpcName := "Remote Network Name" + + awsMockLocal := infra.AwsMock().MockConfigs(scope.Spec.Scope.Aws.AccountId, scope.Spec.Region) + awsMockRemote := infra.AwsMock().MockConfigs(remoteAccountId, remoteRegion) + + By("And Given AWS VPC exists", func() { + awsMockLocal.AddVpc( + localVpcId, + localVpcCidr, + awsutil.Ec2Tags("Name", vpcName), + awsmock.VpcSubnetsFromScope(scope), + ) + }) + + By("And Given AWS route table exists", func() { + awsMockLocal.AddRouteTable( + ptr.To(localMainRouteTable), + ptr.To(localVpcId), + awsutil.Ec2Tags(fmt.Sprintf("kubernetes.io/cluster/%s", vpcName), "1"), + []ec2Types.RouteTableAssociation{ + { + Main: ptr.To(true), + }, + }) + + awsMockLocal.AddRouteTable( + ptr.To(localRouteTable), + ptr.To(localVpcId), + awsutil.Ec2Tags(fmt.Sprintf("kubernetes.io/cluster/%s", vpcName), "1"), + []ec2Types.RouteTableAssociation{}) + }) + + By("And Given AWS remote VPC exists", func() { + awsMockRemote.AddVpc( + remoteVpcId, + remoteVpcCidr, + awsutil.Ec2Tags("Name", remoteVpcName, kymaName, kymaName), + nil, + ) + }) + + By("And Given AWS remote route table exists", func() { + + awsMockRemote.AddRouteTable( + ptr.To(remoteMainRouteTable), + ptr.To(remoteVpcId), + awsutil.Ec2Tags(), + []ec2Types.RouteTableAssociation{ + { + Main: ptr.To(true), + }, + }) + + awsMockRemote.AddRouteTable( + ptr.To(remoteRouteTableTagged), + ptr.To(remoteVpcId), + awsutil.Ec2Tags(kymaName, kymaName), // tag remote route table + []ec2Types.RouteTableAssociation{}) + + awsMockRemote.AddRouteTable( + ptr.To(remoteRouteTable), + ptr.To(remoteVpcId), + awsutil.Ec2Tags(), + []ec2Types.RouteTableAssociation{}) + }) + + localKcpNetworkName := common.KcpNetworkKymaCommonName(scope.Name) + remoteKcpNetworkName := scope.Name + "--remote" + + var localKcpNet *cloudcontrolv1beta1.Network + + By("And Given local KCP Network exists", func() { + localKcpNet = cloudcontrolv1beta1.NewNetworkBuilder(). + WithScope(scope.Name). + WithAwsRef(scope.Spec.Scope.Aws.AccountId, scope.Spec.Region, scope.Spec.Scope.Aws.Network.VPC.Id, localKcpNetworkName). + Build() + Eventually(CreateObj). + WithArguments(infra.Ctx(), infra.KCP().Client(), localKcpNet, WithName(localKcpNetworkName)). + Should(Succeed()) + }) + + By("And Given remote KCP Network is Ready", func() { + Eventually(LoadAndCheck). + WithArguments(infra.Ctx(), infra.KCP().Client(), localKcpNet, + NewObjActions(), + HaveFinalizer(api.CommonFinalizerDeletionHook), + HavingConditionTrue(cloudcontrolv1beta1.ConditionTypeReady), + ).Should(Succeed()) + }) + + var remoteKcpNet *cloudcontrolv1beta1.Network + + By("And Given remote KCP Network exists", func() { + remoteKcpNet = cloudcontrolv1beta1.NewNetworkBuilder(). + WithScope(scope.Name). + WithAwsRef(remoteAccountId, remoteRegion, remoteVpcId, remoteVpcName). + Build() + Eventually(CreateObj). + WithArguments(infra.Ctx(), infra.KCP().Client(), remoteKcpNet, WithName(remoteKcpNetworkName)). + Should(Succeed()) + }) + + By("And Given remote KCP Network is Ready", func() { + Eventually(LoadAndCheck). + WithArguments(infra.Ctx(), infra.KCP().Client(), remoteKcpNet, + NewObjActions(), + HaveFinalizer(api.CommonFinalizerDeletionHook), + HavingConditionTrue(cloudcontrolv1beta1.ConditionTypeReady), + ).Should(Succeed()) + }) + + var kcpPeering *cloudcontrolv1beta1.VpcPeering + + By("And Given KCP VpcPeering is created", func() { + kcpPeering = (&cloudcontrolv1beta1.VpcPeeringBuilder{}). + WithScope(kymaName). + WithRemoteRef("skr-namespace", "skr-aws-ip-range"). + WithDetails(localKcpNetworkName, infra.KCP().Namespace(), remoteKcpNetworkName, infra.KCP().Namespace(), "", false, false). + WithRemoteRouteTableUpdateStrategy(cloudcontrolv1beta1.AwsRouteTableUpdateStrategyMatched). + Build() + + Eventually(CreateObj). + WithArguments(infra.Ctx(), infra.KCP().Client(), kcpPeering, + WithName(kcpPeeringName), + ).Should(Succeed()) + + }) + + By("And Given KCP VpcPeering have status.id set", func() { + Eventually(LoadAndCheck). + WithArguments(infra.Ctx(), infra.KCP().Client(), kcpPeering, + NewObjActions(), + HaveFinalizer(api.CommonFinalizerDeletionHook), + HavingKcpVpcPeeringStatusIdNotEmpty(), + ).Should(Succeed()) + }) + + By("And Given AWS VpcPeeringConnections are active", func() { + + // initiate remote vpc peering connection + awsMockRemote.InitiateVpcPeeringConnection(kcpPeering.Status.Id, localVpcId, remoteVpcId) + + // change local vpc peering status to pending-acceptance (not necessary but leaving it for the clarity) + + Expect( + awsMockLocal.SetVpcPeeringConnectionStatusCode(localVpcId, remoteVpcId, ec2Types.VpcPeeringConnectionStateReasonCodePendingAcceptance), + ).NotTo(HaveOccurred()) + + // sets vpc peering connections active + Expect( + awsMockLocal.SetVpcPeeringConnectionStatusCode(localVpcId, remoteVpcId, ec2Types.VpcPeeringConnectionStateReasonCodeActive), + ).NotTo(HaveOccurred()) + + Expect( + awsMockRemote.SetVpcPeeringConnectionStatusCode(localVpcId, remoteVpcId, ec2Types.VpcPeeringConnectionStateReasonCodeActive), + ).NotTo(HaveOccurred()) + }) + + By("When VpcPeering is Ready", func() { + Eventually(LoadAndCheck, "2s"). + WithArguments(infra.Ctx(), infra.KCP().Client(), kcpPeering, + NewObjActions(), + HavingConditionTrue(cloudcontrolv1beta1.ConditionTypeReady)). + Should(Succeed()) + }) + + By("Then remote VpcPeeringConnection exists", func() { + remotePeering, _ := awsMockRemote.DescribeVpcPeeringConnection(infra.Ctx(), kcpPeering.Status.Id) + Expect(remotePeering).NotTo(BeNil()) + }) + + By("And Then remote tagged route table has route with destination CIDR matching local VPC CIDR", func() { + Expect(CheckRoute(awsMockRemote, remoteVpcId, remoteRouteTableTagged, kcpPeering.Status.RemoteId, localVpcCidr)). + Should(Succeed()) + }) + + By("And Then remote untagged route table does not have destination CIDR matching local VPC CIDR", func() { + Expect(CheckRoute(awsMockRemote, remoteVpcId, remoteRouteTable, kcpPeering.Status.RemoteId, localVpcCidr)). + ShouldNot(Succeed()) + }) + + // DELETE + + By("When KCP VpcPeering is deleted", func() { + Eventually(Delete). + WithArguments(infra.Ctx(), infra.KCP().Client(), kcpPeering). + Should(Succeed(), "failed deleting VpcPeering") + }) + + By("Then VpcPeering does not exist", func() { + Eventually(IsDeleted). + WithArguments(infra.Ctx(), infra.KCP().Client(), kcpPeering). + Should(Succeed(), "expected VpcPeering not to exist (be deleted), but it still exists") + }) + + By("And Then local VpcPeeringConnection is deleted", func() { + localPeering, _ := awsMockLocal.DescribeVpcPeeringConnection(infra.Ctx(), kcpPeering.Status.Id) + Expect(localPeering).To(BeNil()) + }) + + By("And Then remote VpcPeeringConnection is not deleted", func() { + remotePeering, _ := awsMockRemote.DescribeVpcPeeringConnection(infra.Ctx(), kcpPeering.Status.Id) + Expect(remotePeering).NotTo(BeNil()) + }) + + By("And Then all remote route tables has one new route with destination CIDR matching VPC CIDR", func() { + Expect(awsMockRemote.GetRoute(remoteVpcId, remoteRouteTableTagged, kcpPeering.Status.Id, localVpcCidr)). + NotTo(BeNil()) + }) + + }) + }) diff --git a/pkg/kcp/provider/aws/meta/metadata.go b/pkg/kcp/provider/aws/meta/metadata.go index 23907d1a6..7917869b7 100644 --- a/pkg/kcp/provider/aws/meta/metadata.go +++ b/pkg/kcp/provider/aws/meta/metadata.go @@ -19,6 +19,7 @@ import ( const ( UnauthorizedOperation = "UnauthorizedOperation" AccessDenied = "AccessDenied" + RouteNotSupported = "RouteNotSupported" ) type awsAccountKeyType struct{} @@ -55,18 +56,20 @@ func AsApiError(err error) smithy.APIError { return nil } -func GetErrorMessage(err error) string { +func GetErrorMessage(err error, def string) (string, bool) { var apiError smithy.APIError if errors.As(err, &apiError) { switch apiError.ErrorCode() { case UnauthorizedOperation: - return "Not authorized to perform this operation." + return "Not authorized to perform this operation.", true case AccessDenied: - return "Not authorized to assume role." + return "Not authorized to assume role.", true + case RouteNotSupported: + return "Route not supported.", true } - return apiError.ErrorMessage() } - return err.Error() + + return def, false } func IsAccessDenied(err error) bool { @@ -85,6 +88,14 @@ func IsUnauthorized(err error) bool { return false } +func IsRouteNotSupported(err error) bool { + var apiError smithy.APIError + if errors.As(err, &apiError) { + return apiError.ErrorCode() == RouteNotSupported + } + return false +} + var notFoundErrorCodes = map[string]struct{}{ (&efsTypes.FileSystemNotFound{}).ErrorCode(): {}, (&efsTypes.AccessPointNotFound{}).ErrorCode(): {}, diff --git a/pkg/kcp/provider/aws/mock/routeTablesStore.go b/pkg/kcp/provider/aws/mock/routeTablesStore.go index 6aa95924b..2aab07de9 100644 --- a/pkg/kcp/provider/aws/mock/routeTablesStore.go +++ b/pkg/kcp/provider/aws/mock/routeTablesStore.go @@ -10,8 +10,8 @@ import ( type RouteTableConfig interface { AddRouteTable(routeTableId, vpcId *string, tags []ec2types.Tag, associations []ec2types.RouteTableAssociation) ec2types.RouteTable - GetRoute(ctc context.Context, vpcId, routeTableId, vpcPeeringConnectionId, destinationCidrBlock string) *ec2types.Route - GetRouteCount(ctx context.Context, vpcId, vpcPeeringConnectionId, destinationCidrBlock string) int + GetRoute(vpcId, routeTableId, vpcPeeringConnectionId, destinationCidrBlock string) *ec2types.Route + GetRouteCount(vpcId, vpcPeeringConnectionId, destinationCidrBlock string) int } type routeTableEntry struct { routeTable ec2types.RouteTable @@ -46,17 +46,25 @@ func (s *routeTablesStore) AddRouteTable(routeTableId, vpcId *string, tags []ec2 return entry.routeTable } -func (s *routeTablesStore) DescribeRouteTables(ctc context.Context, vpcId string) ([]ec2types.RouteTable, error) { +func (s *routeTablesStore) DescribeRouteTables(ctx context.Context, vpcId string) ([]ec2types.RouteTable, error) { + if isContextCanceled(ctx) { + return nil, context.Canceled + } + s.m.Lock() defer s.m.Unlock() + return s.describeRouteTables(vpcId) +} + +func (s *routeTablesStore) describeRouteTables(vpcId string) ([]ec2types.RouteTable, error) { filtered := pie.Filter(s.items, func(e *routeTableEntry) bool { return *e.routeTable.VpcId == vpcId }) return pie.Map(filtered, func(e *routeTableEntry) ec2types.RouteTable { return e.routeTable }), nil } -func (s *routeTablesStore) GetRoute(ctc context.Context, vpcId, routeTableId, vpcPeeringConnectionId, destinationCidrBlock string) *ec2types.Route { +func (s *routeTablesStore) GetRoute(vpcId, routeTableId, vpcPeeringConnectionId, destinationCidrBlock string) *ec2types.Route { s.m.Lock() defer s.m.Unlock() @@ -73,6 +81,9 @@ func (s *routeTablesStore) GetRoute(ctc context.Context, vpcId, routeTableId, vp return nil } func (s *routeTablesStore) CreateRoute(ctx context.Context, routeTableId, destinationCidrBlock, vpcPeeringConnectionId *string) error { + if isContextCanceled(ctx) { + return context.Canceled + } s.m.Lock() defer s.m.Unlock() @@ -91,6 +102,9 @@ func (s *routeTablesStore) CreateRoute(ctx context.Context, routeTableId, destin } func (s *routeTablesStore) DeleteRoute(ctx context.Context, routeTableId, destinationCidrBlock *string) error { + if isContextCanceled(ctx) { + return context.Canceled + } s.m.Lock() defer s.m.Unlock() @@ -107,8 +121,8 @@ func (s *routeTablesStore) DeleteRoute(ctx context.Context, routeTableId, destin return nil } -func (s *routeTablesStore) GetRouteCount(ctx context.Context, vpcId, vpcPeeringConnectionId, destinationCidrBlock string) int { - tables, err := s.DescribeRouteTables(ctx, vpcId) +func (s *routeTablesStore) GetRouteCount(vpcId, vpcPeeringConnectionId, destinationCidrBlock string) int { + tables, err := s.describeRouteTables(vpcId) if err != nil { return -1 diff --git a/pkg/kcp/provider/aws/util/peering.go b/pkg/kcp/provider/aws/util/peering.go index 976eaeedc..3ffedff83 100644 --- a/pkg/kcp/provider/aws/util/peering.go +++ b/pkg/kcp/provider/aws/util/peering.go @@ -1,6 +1,9 @@ package util -import ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" +import ( + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" +) // Determinates whether VpcPeeringConnection can be deleted based on VPC peering connection lifecycle // https://docs.aws.amazon.com/vpc/latest/peering/vpc-peering-basics.html @@ -15,3 +18,13 @@ func IsTerminated(peering *ec2types.VpcPeeringConnection) bool { return false } + +func ShouldUpdateRouteTable(tags []ec2types.Tag, mode cloudcontrolv1beta1.AwsRouteTableUpdateStrategy, tag string) bool { + return mode == cloudcontrolv1beta1.AwsRouteTableUpdateStrategyAuto || + mode == cloudcontrolv1beta1.AwsRouteTableUpdateStrategyMatched && HasEc2Tag(tags, tag) || + mode == cloudcontrolv1beta1.AwsRouteTableUpdateStrategyUnmatched && !HasEc2Tag(tags, tag) +} + +func IsRouteTableUpdateStrategyNone(mode cloudcontrolv1beta1.AwsRouteTableUpdateStrategy) bool { + return mode == cloudcontrolv1beta1.AwsRouteTableUpdateStrategyNone +} diff --git a/pkg/kcp/provider/aws/util/peering_test.go b/pkg/kcp/provider/aws/util/peering_test.go new file mode 100644 index 000000000..d5581f142 --- /dev/null +++ b/pkg/kcp/provider/aws/util/peering_test.go @@ -0,0 +1,31 @@ +package util + +import ( + "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + shootName string = "c-123123" +) + +func TestAwsRouteTableUpdateStrategyAuto(t *testing.T) { + assert.True(t, ShouldUpdateRouteTable(Ec2Tags(), v1beta1.AwsRouteTableUpdateStrategyAuto, shootName)) +} + +func TestAwsRouteTableUpdateStrategyMatched(t *testing.T) { + assert.True(t, ShouldUpdateRouteTable(Ec2Tags(shootName), v1beta1.AwsRouteTableUpdateStrategyMatched, shootName)) +} + +func TestAwsRouteTableUpdateStrategyMatchedNoTag(t *testing.T) { + assert.False(t, ShouldUpdateRouteTable(Ec2Tags(), v1beta1.AwsRouteTableUpdateStrategyMatched, shootName)) +} + +func TestAwsRouteTableUpdateStrategyUnmatched(t *testing.T) { + assert.True(t, ShouldUpdateRouteTable(Ec2Tags(), v1beta1.AwsRouteTableUpdateStrategyUnmatched, shootName)) +} + +func TestAwsRouteTableUpdateStrategyUnmatchedHasTag(t *testing.T) { + assert.False(t, ShouldUpdateRouteTable(Ec2Tags(shootName), v1beta1.AwsRouteTableUpdateStrategyUnmatched, shootName)) +} diff --git a/pkg/kcp/provider/aws/vpcpeering/createRemoteRoutes.go b/pkg/kcp/provider/aws/vpcpeering/createRemoteRoutes.go index 5299ef55a..c7d5beb72 100644 --- a/pkg/kcp/provider/aws/vpcpeering/createRemoteRoutes.go +++ b/pkg/kcp/provider/aws/vpcpeering/createRemoteRoutes.go @@ -9,6 +9,7 @@ import ( cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" "github.com/kyma-project/cloud-manager/pkg/composed" awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + awsutil "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/util" "github.com/kyma-project/cloud-manager/pkg/util" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,55 +19,77 @@ import ( func createRemoteRoutes(ctx context.Context, st composed.State) (error, context.Context) { state := st.(*State) logger := composed.LoggerFromCtx(ctx) - obj := state.ObjAsVpcPeering() + + if awsutil.IsRouteTableUpdateStrategyNone(state.ObjAsVpcPeering().Spec.Details.RemoteRouteTableUpdateStrategy) { + return nil, nil + } for _, t := range state.remoteRouteTables { + + shouldUpdateRouteTable := awsutil.ShouldUpdateRouteTable(t.Tags, + state.ObjAsVpcPeering().Spec.Details.RemoteRouteTableUpdateStrategy, + state.Scope().Spec.ShootName) + routeExists := pie.Any(t.Routes, func(r types.Route) bool { return ptr.Equal(r.VpcPeeringConnectionId, state.vpcPeering.VpcPeeringConnectionId) && ptr.Equal(r.DestinationCidrBlock, state.vpc.CidrBlock) }) - if !routeExists { - err := state.remoteClient.CreateRoute(ctx, t.RouteTableId, state.vpc.CidrBlock, state.vpcPeering.VpcPeeringConnectionId) + var err error + + lll := logger.WithValues( + "remoteRouteTableId", ptr.Deref(t.RouteTableId, "xxx"), + "destinationCidrBlock", ptr.Deref(state.vpc.CidrBlock, "xxx")) + // Create route if it should exist but it doesn't + if shouldUpdateRouteTable && !routeExists { + err = state.remoteClient.CreateRoute(ctx, t.RouteTableId, state.vpc.CidrBlock, state.vpcPeering.VpcPeeringConnectionId) if err != nil { - routeTableId := ptr.Deref(t.RouteTableId, "") - - logger. - WithValues("remoteRouteTableId", routeTableId). - Error(err, "Failed to create remote route") - - if awsmeta.IsErrorRetryable(err) { - return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil - } - - changed := false - - if meta.SetStatusCondition(obj.Conditions(), metav1.Condition{ - Type: cloudcontrolv1beta1.ConditionTypeError, - Status: metav1.ConditionTrue, - Reason: cloudcontrolv1beta1.ReasonFailedCreatingRoutes, - Message: fmt.Sprintf("Failed creating route for remote route table %s. %s", routeTableId, awsmeta.GetErrorMessage(err)), - }) { - changed = true - } - - if obj.Status.State != string(cloudcontrolv1beta1.StateWarning) { - obj.Status.State = string(cloudcontrolv1beta1.StateWarning) - changed = true - } - - // Do not update status if nothing is changed - if !changed { - return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil - } - - // User can recover by modifying routes - return composed.PatchStatus(obj). - ErrorLogMessage("Error updating VpcPeering status when creating routes"). - SuccessError(composed.StopWithRequeueDelay(util.Timing.T60000ms())). - Run(ctx, state) + lll.Error(err, "Error creating remote route") + } else { + lll.Info("Remote route created") + } + } + + if err != nil { + + if awsmeta.IsErrorRetryable(err) { + return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil + } + + successError := composed.StopWithRequeueDelay(util.Timing.T60000ms()) + + if awsmeta.IsRouteNotSupported(err) { + successError = composed.StopAndForget + } + + changed := false + + msg, _ := awsmeta.GetErrorMessage(err, "") + if meta.SetStatusCondition(state.ObjAsVpcPeering().Conditions(), metav1.Condition{ + Type: cloudcontrolv1beta1.ConditionTypeError, + Status: metav1.ConditionTrue, + Reason: cloudcontrolv1beta1.ReasonFailedCreatingRoutes, + Message: fmt.Sprintf("Failed updating routes for remote route table %s. %s", ptr.Deref(t.RouteTableId, ""), msg), + }) { + changed = true } + + if state.ObjAsVpcPeering().Status.State != string(cloudcontrolv1beta1.StateWarning) { + state.ObjAsVpcPeering().Status.State = string(cloudcontrolv1beta1.StateWarning) + changed = true + } + + // Do not update status if nothing is changed + if !changed { + return successError, nil + } + + // User can recover by modifying routes + return composed.PatchStatus(state.ObjAsVpcPeering()). + ErrorLogMessage("Error updating VpcPeering status when updating routes"). + SuccessError(successError). + Run(ctx, state) } } diff --git a/pkg/kcp/provider/aws/vpcpeering/createVpcPeeringConnection.go b/pkg/kcp/provider/aws/vpcpeering/createVpcPeeringConnection.go index 9df6644b3..ea8325fba 100644 --- a/pkg/kcp/provider/aws/vpcpeering/createVpcPeeringConnection.go +++ b/pkg/kcp/provider/aws/vpcpeering/createVpcPeeringConnection.go @@ -69,11 +69,12 @@ func createVpcPeeringConnection(ctx context.Context, st composed.State) (error, changed = true } + msg, _ := awsmeta.GetErrorMessage(err, "") if meta.SetStatusCondition(state.ObjAsVpcPeering().Conditions(), metav1.Condition{ Type: cloudcontrolv1beta1.ConditionTypeError, Status: metav1.ConditionTrue, Reason: cloudcontrolv1beta1.ReasonFailedCreatingVpcPeeringConnection, - Message: fmt.Sprintf("Failed creating VpcPeerings. %s", awsmeta.GetErrorMessage(err)), + Message: fmt.Sprintf("Failed creating VpcPeerings. %s", msg), }) { changed = true } diff --git a/pkg/kcp/provider/aws/vpcpeering/loadRemoteVpc.go b/pkg/kcp/provider/aws/vpcpeering/loadRemoteVpc.go index adacda86b..acc7e4f7c 100644 --- a/pkg/kcp/provider/aws/vpcpeering/loadRemoteVpc.go +++ b/pkg/kcp/provider/aws/vpcpeering/loadRemoteVpc.go @@ -39,11 +39,13 @@ func loadRemoteVpc(ctx context.Context, st composed.State) (error, context.Conte logger.Error(err, "Error loading remote AWS VPC Networks") + msg, _ := awsmeta.GetErrorMessage(err, "") + condition := metav1.Condition{ Type: cloudcontrolv1beta1.ConditionTypeError, Status: metav1.ConditionTrue, Reason: cloudcontrolv1beta1.ReasonVpcNotFound, - Message: awsmeta.GetErrorMessage(err), + Message: msg, } successError := composed.StopAndForget diff --git a/pkg/kcp/provider/aws/vpcpeering/remoteRoutesDelete.go b/pkg/kcp/provider/aws/vpcpeering/remoteRoutesDelete.go index b0ff59b5d..462cbc96d 100644 --- a/pkg/kcp/provider/aws/vpcpeering/remoteRoutesDelete.go +++ b/pkg/kcp/provider/aws/vpcpeering/remoteRoutesDelete.go @@ -4,6 +4,7 @@ import ( "context" "github.com/kyma-project/cloud-manager/pkg/composed" awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + awsutil "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/util" "github.com/kyma-project/cloud-manager/pkg/util" "k8s.io/utils/ptr" ) @@ -13,6 +14,7 @@ func remoteRoutesDelete(ctx context.Context, st composed.State) (error, context. logger := composed.LoggerFromCtx(ctx) if !state.ObjAsVpcPeering().Spec.Details.DeleteRemotePeering { + logger.Info("Skipping route deletion") return nil, nil } @@ -22,20 +24,30 @@ func remoteRoutesDelete(ctx context.Context, st composed.State) (error, context. } for _, t := range state.remoteRouteTables { + + shouldUpdateRouteTable := awsutil.ShouldUpdateRouteTable(t.Tags, + state.ObjAsVpcPeering().Spec.Details.RemoteRouteTableUpdateStrategy, + state.Scope().Spec.ShootName) + + if !shouldUpdateRouteTable { + continue + } + for _, r := range t.Routes { + if ptr.Equal(r.VpcPeeringConnectionId, state.remoteVpcPeering.VpcPeeringConnectionId) { err := state.remoteClient.DeleteRoute(ctx, t.RouteTableId, r.DestinationCidrBlock) - if awsmeta.IsErrorRetryable(err) { - return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil - } - lll := logger.WithValues( - "routeTableId", ptr.Deref(t.RouteTableId, "xxx"), + "remoteRouteTableId", ptr.Deref(t.RouteTableId, "xxx"), "destinationCidrBlock", ptr.Deref(r.DestinationCidrBlock, "xxx"), ) if err != nil { + if awsmeta.IsErrorRetryable(err) { + return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil + } + lll.Error(err, "Error deleting remote route") return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil } diff --git a/pkg/skr/awsvpcpeering/createKcpVpcPeering.go b/pkg/skr/awsvpcpeering/createKcpVpcPeering.go index 7078c8566..f21807476 100644 --- a/pkg/skr/awsvpcpeering/createKcpVpcPeering.go +++ b/pkg/skr/awsvpcpeering/createKcpVpcPeering.go @@ -52,6 +52,7 @@ func createKcpVpcPeering(ctx context.Context, st composed.State) (error, context Name: common.KcpNetworkKymaCommonName(state.KymaRef.Name), Namespace: state.KymaRef.Namespace, }, + RemoteRouteTableUpdateStrategy: cloudcontrolv1beta1.AwsRouteTableUpdateStrategy(obj.Spec.RemoteRouteTableUpdateStrategy), }, }, } diff --git a/pkg/testinfra/dsl/vpcPeering.go b/pkg/testinfra/dsl/vpcPeering.go index cc11735d7..e9a82647d 100644 --- a/pkg/testinfra/dsl/vpcPeering.go +++ b/pkg/testinfra/dsl/vpcPeering.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + awsmock "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/mock" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -32,3 +33,20 @@ func HavingVpcPeeringStatusRemoteId() ObjAssertion { return nil } } + +func WithRemoteRouteTableUpdateStrategy(strategy cloudcontrolv1beta1.AwsRouteTableUpdateStrategy) ObjAction { + return &objAction{ + f: func(obj client.Object) { + x := obj.(*cloudcontrolv1beta1.VpcPeering) + x.Spec.Details.RemoteRouteTableUpdateStrategy = strategy + }, + } +} + +func CheckRoute(config awsmock.RouteTableConfig, vpcId, routeTableId, vpcPeeringConnectionId, destinationCidrBlock string) error { + route := config.GetRoute(vpcId, routeTableId, vpcPeeringConnectionId, destinationCidrBlock) + if route == nil { + return errors.New("route does not exist") + } + return nil +}