From d52c95b58a16af0ad4d015c8b5dfdd86b45f51ca Mon Sep 17 00:00:00 2001 From: Bruno Jerosch Date: Wed, 3 Jul 2024 14:30:38 +0100 Subject: [PATCH] feat(GCPVpcPeering) Delete VPC Peering --- api/cloud-control/v1beta1/vpcpeering_types.go | 2 +- .../v1beta1/gcpvpcpeering_types.go | 16 +- ...d-control.kyma-project.io_vpcpeerings.yaml | 2 +- ...ources.kyma-project.io_gcpvpcpeerings.yaml | 14 +- ...d-control.kyma-project.io_vpcpeerings.yaml | 2 +- ...ources.kyma-project.io_gcpvpcpeerings.yaml | 14 +- ...ces.kyma-project.io_gcpvpcpeerings_ui.yaml | 14 +- ...cloud-resources_v1beta1_gcpvpcpeering.yaml | 2 +- ...ces.kyma-project.io_gcpvpcpeerings_ui.yaml | 14 +- config/ui-extensions/gcpvpcpeerings/details | 4 +- config/ui-extensions/gcpvpcpeerings/form | 4 +- config/ui-extensions/gcpvpcpeerings/list | 4 +- .../ui-extensions/gcpvpcpeerings/translations | 2 +- .../cloud-control/vpcpeering_gcp_test.go | 13 +- pkg/kcp/provider/gcp/mock/vpcPeeringStore.go | 96 ++++++++-- .../provider/gcp/vpcpeering/addFinalizer.go | 30 ---- ...udComputeClient.go => vpcPeeringClient.go} | 167 ++++++++++-------- .../gcp/vpcpeering/createKymaVpcPeering.go | 93 ++++++++++ .../gcp/vpcpeering/createRemoteVpcPeering.go | 73 ++++++++ .../gcp/vpcpeering/createVpcPeering.go | 75 -------- .../gcp/vpcpeering/deleteVpcPeering.go | 15 +- .../gcp/vpcpeering/loadKymaVpcPeering.go | 45 +++++ .../gcp/vpcpeering/loadRemoteVpcPeering.go | 45 +++++ pkg/kcp/provider/gcp/vpcpeering/new.go | 36 ++-- .../gcp/vpcpeering/removeFinalizer.go | 27 --- pkg/kcp/provider/gcp/vpcpeering/state.go | 23 +-- .../provider/gcp/vpcpeering/updateStatus.go | 12 +- .../vpcpeering/waitKymaVpcPeeringDeletion.go | 19 ++ .../waitRemoteVpcPeeringAvailable.go | 20 +++ .../gcp/vpcpeering/waitVpcPeeringActive.go | 20 +++ pkg/skr/gcpvpcpeering/createKcpVpcPeering.go | 8 +- pkg/skr/gcpvpcpeering/reconciler.go | 21 ++- pkg/skr/gcpvpcpeering/waitKcpStatusUpdate.go | 17 ++ pkg/skr/gcpvpcpeering/waitSkrStatusReady.go | 18 ++ pkg/testinfra/dsl/kcpVpcPeering.go | 4 +- .../gcpvpcpeering/kcp-gcp-vpcpeering.yaml | 2 +- .../gcpvpcpeering/skr-gcp-vpcpeering.yaml | 2 +- 37 files changed, 663 insertions(+), 312 deletions(-) delete mode 100644 pkg/kcp/provider/gcp/vpcpeering/addFinalizer.go rename pkg/kcp/provider/gcp/vpcpeering/client/{cloudComputeClient.go => vpcPeeringClient.go} (53%) create mode 100644 pkg/kcp/provider/gcp/vpcpeering/createKymaVpcPeering.go create mode 100644 pkg/kcp/provider/gcp/vpcpeering/createRemoteVpcPeering.go delete mode 100644 pkg/kcp/provider/gcp/vpcpeering/createVpcPeering.go create mode 100644 pkg/kcp/provider/gcp/vpcpeering/loadKymaVpcPeering.go create mode 100644 pkg/kcp/provider/gcp/vpcpeering/loadRemoteVpcPeering.go delete mode 100644 pkg/kcp/provider/gcp/vpcpeering/removeFinalizer.go create mode 100644 pkg/kcp/provider/gcp/vpcpeering/waitKymaVpcPeeringDeletion.go create mode 100644 pkg/kcp/provider/gcp/vpcpeering/waitRemoteVpcPeeringAvailable.go create mode 100644 pkg/kcp/provider/gcp/vpcpeering/waitVpcPeeringActive.go create mode 100644 pkg/skr/gcpvpcpeering/waitKcpStatusUpdate.go create mode 100644 pkg/skr/gcpvpcpeering/waitSkrStatusReady.go diff --git a/api/cloud-control/v1beta1/vpcpeering_types.go b/api/cloud-control/v1beta1/vpcpeering_types.go index bbaf8a7b6..d7957e25b 100644 --- a/api/cloud-control/v1beta1/vpcpeering_types.go +++ b/api/cloud-control/v1beta1/vpcpeering_types.go @@ -55,7 +55,7 @@ type VpcPeeringInfo struct { } type GcpVpcPeering struct { - PeeringName string `json:"peeringName,omitempty"` + RemotePeeringName string `json:"remotePeeringName,omitempty"` RemoteProject string `json:"remoteProject,omitempty"` RemoteVpc string `json:"remoteVpc,omitempty"` ImportCustomRoutes bool `json:"importCustomRoutes,omitempty"` diff --git a/api/cloud-resources/v1beta1/gcpvpcpeering_types.go b/api/cloud-resources/v1beta1/gcpvpcpeering_types.go index 8d9d92bd5..37d7b5c92 100644 --- a/api/cloud-resources/v1beta1/gcpvpcpeering_types.go +++ b/api/cloud-resources/v1beta1/gcpvpcpeering_types.go @@ -9,10 +9,18 @@ import ( // Important: Run "make" to regenerate code after modifying this file type GcpVpcPeeringSpec struct { - ImportCustomRoutes bool `json:"importCustomRoutes,omitempty"` - PeeringName string `json:"peeringName,omitempty"` - RemoteVpc string `json:"remoteVpc,omitempty"` - RemoteProject string `json:"remoteProject,omitempty"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="ImportCustomRoutes is immutable." + ImportCustomRoutes bool `json:"importCustomRoutes,omitempty"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemotePeeringName is immutable." + RemotePeeringName string `json:"remotePeeringName,omitempty"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemoteVpc is immutable." + RemoteVpc string `json:"remoteVpc,omitempty"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemoteNetwork is immutable." + RemoteProject string `json:"remoteProject,omitempty"` } type GcpVpcPeeringStatus struct { 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 bfa51946b..c2dd81e51 100644 --- a/config/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml +++ b/config/crd/bases/cloud-control.kyma-project.io_vpcpeerings.yaml @@ -80,7 +80,7 @@ spec: properties: importCustomRoutes: type: boolean - peeringName: + remotePeeringName: type: string remoteProject: type: string diff --git a/config/crd/bases/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml b/config/crd/bases/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml index 037bbd542..7467af607 100644 --- a/config/crd/bases/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml +++ b/config/crd/bases/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml @@ -34,12 +34,24 @@ spec: properties: importCustomRoutes: type: boolean - peeringName: + x-kubernetes-validations: + - message: ImportCustomRoutes is immutable. + rule: (self == oldSelf) + remotePeeringName: type: string + x-kubernetes-validations: + - message: RemotePeeringName is immutable. + rule: (self == oldSelf) remoteProject: type: string + x-kubernetes-validations: + - message: RemoteNetwork is immutable. + rule: (self == oldSelf) remoteVpc: type: string + x-kubernetes-validations: + - message: RemoteVpc is immutable. + rule: (self == oldSelf) type: object status: properties: 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 bfa51946b..c2dd81e51 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 @@ -80,7 +80,7 @@ spec: properties: importCustomRoutes: type: boolean - peeringName: + remotePeeringName: type: string remoteProject: type: string diff --git a/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml b/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml index 037bbd542..7467af607 100644 --- a/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml +++ b/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings.yaml @@ -34,12 +34,24 @@ spec: properties: importCustomRoutes: type: boolean - peeringName: + x-kubernetes-validations: + - message: ImportCustomRoutes is immutable. + rule: (self == oldSelf) + remotePeeringName: type: string + x-kubernetes-validations: + - message: RemotePeeringName is immutable. + rule: (self == oldSelf) remoteProject: type: string + x-kubernetes-validations: + - message: RemoteNetwork is immutable. + rule: (self == oldSelf) remoteVpc: type: string + x-kubernetes-validations: + - message: RemoteVpc is immutable. + rule: (self == oldSelf) type: object status: properties: diff --git a/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml b/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml index cb06fd8fe..dc31cc903 100644 --- a/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml +++ b/config/dist/skr/crd/bases/providers/gcp/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml @@ -10,8 +10,8 @@ data: source: importCustomRoutes name: spec.importCustomRoutes - widget: Labels - source: peeringName - name: spec.peeringName + source: remotePeeringName + name: spec.remotePeeringName - widget: Labels source: remoteProject name: spec.remoteProject @@ -29,9 +29,9 @@ data: - path: spec.importCustomRoutes simple: true name: spec.importCustomRoutes - - path: spec.peeringName + - path: spec.remotePeeringName simple: true - name: spec.peeringName + name: spec.remotePeeringName widget: Text - path: spec.remoteProject simple: true @@ -57,8 +57,8 @@ data: - source: spec.importCustomRoutes name: spec.importCustomRoutes sort: true - - source: spec.peeringName - name: spec.peeringName + - source: spec.remotePeeringName + name: spec.remotePeeringName sort: true - source: spec.remoteProject name: spec.remoteProject @@ -71,7 +71,7 @@ data: configuration: Configuration status: Status spec.importCustomRoutes: Import Custom Routes - spec.peeringName: Peering Name + spec.remotePeeringName: Remote Peering Name spec.remoteProject: Remote Project spec.remoteVpc: Remote VPC status.id: ID diff --git a/config/samples/cloud-resources_v1beta1_gcpvpcpeering.yaml b/config/samples/cloud-resources_v1beta1_gcpvpcpeering.yaml index a6d00bd17..0f27c496b 100644 --- a/config/samples/cloud-resources_v1beta1_gcpvpcpeering.yaml +++ b/config/samples/cloud-resources_v1beta1_gcpvpcpeering.yaml @@ -9,7 +9,7 @@ metadata: app.kubernetes.io/created-by: cloud-manager name: gcpvpcpeering-sample spec: - name: "vpcpeering-sap-gcp-skr-dev-cust-00002-sap-sc-learn" + remotePeeringame: "vpcpeering-sap-gcp-skr-dev-cust-00002-sap-sc-learn" remoteProject: "sap-sc-learn" remoteVpc: "default" importCustomRoutes: false diff --git a/config/ui-extensions/gcpvpcpeerings/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml b/config/ui-extensions/gcpvpcpeerings/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml index cb06fd8fe..dc31cc903 100644 --- a/config/ui-extensions/gcpvpcpeerings/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml +++ b/config/ui-extensions/gcpvpcpeerings/cloud-resources.kyma-project.io_gcpvpcpeerings_ui.yaml @@ -10,8 +10,8 @@ data: source: importCustomRoutes name: spec.importCustomRoutes - widget: Labels - source: peeringName - name: spec.peeringName + source: remotePeeringName + name: spec.remotePeeringName - widget: Labels source: remoteProject name: spec.remoteProject @@ -29,9 +29,9 @@ data: - path: spec.importCustomRoutes simple: true name: spec.importCustomRoutes - - path: spec.peeringName + - path: spec.remotePeeringName simple: true - name: spec.peeringName + name: spec.remotePeeringName widget: Text - path: spec.remoteProject simple: true @@ -57,8 +57,8 @@ data: - source: spec.importCustomRoutes name: spec.importCustomRoutes sort: true - - source: spec.peeringName - name: spec.peeringName + - source: spec.remotePeeringName + name: spec.remotePeeringName sort: true - source: spec.remoteProject name: spec.remoteProject @@ -71,7 +71,7 @@ data: configuration: Configuration status: Status spec.importCustomRoutes: Import Custom Routes - spec.peeringName: Peering Name + spec.remotePeeringName: Remote Peering Name spec.remoteProject: Remote Project spec.remoteVpc: Remote VPC status.id: ID diff --git a/config/ui-extensions/gcpvpcpeerings/details b/config/ui-extensions/gcpvpcpeerings/details index 3bc66155e..8045e2a92 100644 --- a/config/ui-extensions/gcpvpcpeerings/details +++ b/config/ui-extensions/gcpvpcpeerings/details @@ -7,8 +7,8 @@ body: source: importCustomRoutes name: spec.importCustomRoutes - widget: Labels - source: peeringName - name: spec.peeringName + source: remotePeeringName + name: spec.remotePeeringName - widget: Labels source: remoteProject name: spec.remoteProject diff --git a/config/ui-extensions/gcpvpcpeerings/form b/config/ui-extensions/gcpvpcpeerings/form index a67807092..4ef6f1db9 100644 --- a/config/ui-extensions/gcpvpcpeerings/form +++ b/config/ui-extensions/gcpvpcpeerings/form @@ -1,9 +1,9 @@ - path: spec.importCustomRoutes simple: true name: spec.importCustomRoutes -- path: spec.peeringName +- path: spec.remotePeeringName simple: true - name: spec.peeringName + name: spec.remotePeeringName widget: Text - path: spec.remoteProject simple: true diff --git a/config/ui-extensions/gcpvpcpeerings/list b/config/ui-extensions/gcpvpcpeerings/list index fa474c4e0..72e677147 100644 --- a/config/ui-extensions/gcpvpcpeerings/list +++ b/config/ui-extensions/gcpvpcpeerings/list @@ -1,8 +1,8 @@ - source: spec.importCustomRoutes name: spec.importCustomRoutes sort: true -- source: spec.peeringName - name: spec.peeringName +- source: spec.remotePeeringName + name: spec.remotePeeringName sort: true - source: spec.remoteProject name: spec.remoteProject diff --git a/config/ui-extensions/gcpvpcpeerings/translations b/config/ui-extensions/gcpvpcpeerings/translations index 7f275cde8..d290fe846 100644 --- a/config/ui-extensions/gcpvpcpeerings/translations +++ b/config/ui-extensions/gcpvpcpeerings/translations @@ -2,7 +2,7 @@ en: configuration: Configuration status: Status spec.importCustomRoutes: Import Custom Routes - spec.peeringName: Peering Name + spec.remotePeeringName: Remote Peering Name spec.remoteProject: Remote Project spec.remoteVpc: Remote VPC status.id: ID \ No newline at end of file diff --git a/internal/controller/cloud-control/vpcpeering_gcp_test.go b/internal/controller/cloud-control/vpcpeering_gcp_test.go index ade68f307..c4537d1bf 100644 --- a/internal/controller/cloud-control/vpcpeering_gcp_test.go +++ b/internal/controller/cloud-control/vpcpeering_gcp_test.go @@ -6,14 +6,13 @@ import ( . "github.com/kyma-project/cloud-manager/pkg/testinfra/dsl" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "time" ) var _ = Describe("Feature: KCP VpcPeering", func() { It("Scenario: KCP GCP VpcPeering is created", func() { const ( kymaName = "57bc9639-d752-4f67-8b9e-7cd12514575f" - peeringName = "peering-sap-gcp-skr-dev-cust-00002-to-sap-sc-learn" + remotePeeringName = "peering-sap-gcp-skr-dev-cust-00002-to-sap-sc-learn" remoteVpc = "default" remoteProject = "sap-sc-learn" remoteRefNamespace = "kcp-system" @@ -37,10 +36,10 @@ var _ = Describe("Feature: KCP VpcPeering", func() { By("When KCP VpcPeering is created", func() { Eventually(CreateKcpVpcPeering). WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering, - WithName(peeringName), + WithName(remotePeeringName), WithKcpVpcPeeringRemoteRef(remoteRefNamespace, remoteRefName), WithKcpVpcPeeringSpecScope(kymaName), - WithKcpVpcPeeringSpecGCP(remoteVpc, remoteProject, peeringName, importCustomRoutes), + WithKcpVpcPeeringSpecGCP(remoteVpc, remoteProject, remotePeeringName, importCustomRoutes), ). Should(Succeed()) }) @@ -59,13 +58,13 @@ var _ = Describe("Feature: KCP VpcPeering", func() { By("When KCP VpcPeering is deleted", func() { Eventually(Delete). WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering). - Should(Succeed(), "deleting VpcPeering failed") + Should(Succeed(), "Error deleting VPC Peering") }) By("Then VpcPeering does not exist", func() { - Eventually(IsDeleted, 5*time.Second). + Eventually(IsDeleted). WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering). - Should(Succeed(), "expected VpcPeering does not to exist (being deleted), but it still exists") + Should(Succeed(), "VPC Peering was not deleted") }) }) diff --git a/pkg/kcp/provider/gcp/mock/vpcPeeringStore.go b/pkg/kcp/provider/gcp/mock/vpcPeeringStore.go index 8a23af851..a11987476 100644 --- a/pkg/kcp/provider/gcp/mock/vpcPeeringStore.go +++ b/pkg/kcp/provider/gcp/mock/vpcPeeringStore.go @@ -4,7 +4,7 @@ import ( compute "cloud.google.com/go/compute/apiv1" pb "cloud.google.com/go/compute/apiv1/computepb" "context" - "github.com/elliotchance/pie/v2" + "fmt" "k8s.io/utils/ptr" "sync" ) @@ -14,41 +14,103 @@ type vpcPeeringEntry struct { } type vpcPeeringStore struct { m sync.Mutex - items []*vpcPeeringEntry + items map[string]*vpcPeeringEntry } -func (s *vpcPeeringStore) CreateVpcPeering(ctx context.Context, name *string, remoteVpc *string, remoteProject *string, importCustomRoutes *bool, kymaProject *string, kymaVpc *string) (*pb.NetworkPeering, error) { +func getFullNetworkUrl(project, vpc string) string { + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s", project, vpc) +} + +func (s *vpcPeeringStore) CreateRemoteVpcPeering(ctx context.Context, remotePeeringName string, remoteVpc string, remoteProject string, importCustomRoutes bool, kymaProject string, kymaVpc string) (*compute.Operation, error) { s.m.Lock() defer s.m.Unlock() + remoteNetwork := getFullNetworkUrl(remoteProject, remoteVpc) + kymaNetwork := getFullNetworkUrl(kymaProject, kymaVpc) + + _, peeringExists := s.items[remoteNetwork] + if peeringExists { + return new(compute.Operation), nil + } + + state := pb.NetworkPeering_ACTIVE.String() item := &vpcPeeringEntry{ peering: &pb.NetworkPeering{ - Name: name, - Network: remoteVpc, - ImportCustomRoutes: importCustomRoutes, + Name: &remotePeeringName, + Network: &kymaNetwork, + ImportCustomRoutes: &importCustomRoutes, ExchangeSubnetRoutes: ptr.To(true), }, } + item.peering.State = &state + s.items[remoteNetwork] = item - s.items = append(s.items, item) + return new(compute.Operation), nil +} + +func (s *vpcPeeringStore) CreateKymaVpcPeering(ctx context.Context, remotePeeringName string, remoteVpc string, remoteProject string, importCustomRoutes bool, kymaProject string, kymaVpc string) (*compute.Operation, error) { + s.m.Lock() + defer s.m.Unlock() - return item.peering, nil + remoteNetwork := getFullNetworkUrl(remoteProject, remoteVpc) + kymaNetwork := getFullNetworkUrl(kymaProject, kymaVpc) + + _, peeringExists := s.items[kymaNetwork] + if peeringExists { + return new(compute.Operation), nil + } + + state := pb.NetworkPeering_ACTIVE.String() + + item := &vpcPeeringEntry{ + peering: &pb.NetworkPeering{ + Name: &remotePeeringName, + Network: &remoteNetwork, + ImportCustomRoutes: &importCustomRoutes, + ExchangeSubnetRoutes: ptr.To(true), + }, + } + item.peering.State = &state + + s.items[kymaNetwork] = item + + return new(compute.Operation), nil +} + +func (s *vpcPeeringStore) CheckRemoteNetworkTags(context context.Context, remoteVpc string, remoteProject string, desiredTag string) (bool, error) { + s.m.Lock() + defer s.m.Unlock() + + return true, nil } -func (s *vpcPeeringStore) DeleteVpcPeering(ctx context.Context, name *string, kymaProject *string, kymaVpc *string) (*compute.Operation, error) { +func (s *vpcPeeringStore) GetVpcPeering(ctx context.Context, remotePeeringName string, project string, vpc string) (*pb.NetworkPeering, error) { s.m.Lock() defer s.m.Unlock() - s.items = pie.Filter(s.items, func(vpe *vpcPeeringEntry) bool { - return !(vpe.peering.Name == name && *vpe.peering.Network == "https://www.googleapis.com/compute/v1/projects/"+*kymaProject+"/global/networks/"+*kymaVpc) - }) - return nil, nil + + if s.items == nil { + s.items = make(map[string]*vpcPeeringEntry) + } + + network := getFullNetworkUrl(project, vpc) + + _, peeringExists := s.items[network] + if !peeringExists { + return nil, nil + } + + return s.items[network].peering, nil } -func (s *vpcPeeringStore) DescribeVpcPeeringConnections(ctx context.Context) ([]*pb.NetworkPeering, error) { +func (s *vpcPeeringStore) DeleteVpcPeering(ctx context.Context, remotePeeringName string, kymaProject string, kymaVpc string) (*compute.Operation, error) { s.m.Lock() defer s.m.Unlock() - return pie.Map(s.items, func(e *vpcPeeringEntry) *pb.NetworkPeering { - return e.peering - }), nil + kymaNetwork := getFullNetworkUrl(kymaProject, kymaVpc) + + if s.items[kymaNetwork] == nil { + return nil, nil + } + delete(s.items, kymaNetwork) + return new(compute.Operation), nil } diff --git a/pkg/kcp/provider/gcp/vpcpeering/addFinalizer.go b/pkg/kcp/provider/gcp/vpcpeering/addFinalizer.go deleted file mode 100644 index 5a9499056..000000000 --- a/pkg/kcp/provider/gcp/vpcpeering/addFinalizer.go +++ /dev/null @@ -1,30 +0,0 @@ -package vpcpeering - -import ( - "context" - cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" - "github.com/kyma-project/cloud-manager/pkg/composed" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func addFinalizer(ctx context.Context, state composed.State) (error, context.Context) { - // Object is being deleted, don't add finalizer - if composed.MarkedForDeletionPredicate(ctx, state) { - return nil, nil - } - - // If finalizer already present, don't add it again. - if controllerutil.ContainsFinalizer(state.Obj(), cloudcontrolv1beta1.FinalizerName) { - return nil, nil - } - - //Add finalizer - controllerutil.AddFinalizer(state.Obj(), cloudcontrolv1beta1.FinalizerName) - - if err := state.UpdateObj(ctx); err != nil { - return composed.LogErrorAndReturn(err, "Error adding Finalizer", composed.StopWithRequeue, ctx) - } - - // Requeue to reload the object - return composed.StopWithRequeue, nil -} diff --git a/pkg/kcp/provider/gcp/vpcpeering/client/cloudComputeClient.go b/pkg/kcp/provider/gcp/vpcpeering/client/vpcPeeringClient.go similarity index 53% rename from pkg/kcp/provider/gcp/vpcpeering/client/cloudComputeClient.go rename to pkg/kcp/provider/gcp/vpcpeering/client/vpcPeeringClient.go index b521c431d..2ef0b45cf 100644 --- a/pkg/kcp/provider/gcp/vpcpeering/client/cloudComputeClient.go +++ b/pkg/kcp/provider/gcp/vpcpeering/client/vpcPeeringClient.go @@ -1,19 +1,3 @@ -package client - -import ( - compute "cloud.google.com/go/compute/apiv1" - pb "cloud.google.com/go/compute/apiv1/computepb" - resourcemanager "cloud.google.com/go/resourcemanager/apiv3" - "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" - "context" - "fmt" - "github.com/kyma-project/cloud-manager/pkg/common/abstractions" - "github.com/kyma-project/cloud-manager/pkg/kcp/provider/gcp/cloudclient" - "google.golang.org/api/option" - "k8s.io/utils/ptr" - "strings" -) - /* required GCP permissions ========================= @@ -30,6 +14,24 @@ required GCP permissions compute.networks.ListEffectiveTags => https://cloud.google.com/resource-manager/reference/rest/v3/tagKeys/get */ +package client + +import ( + compute "cloud.google.com/go/compute/apiv1" + pb "cloud.google.com/go/compute/apiv1/computepb" + resourcemanager "cloud.google.com/go/resourcemanager/apiv3" + "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" + "context" + "fmt" + "github.com/elliotchance/pie/v2" + "github.com/kyma-project/cloud-manager/pkg/common/abstractions" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/kcp/provider/gcp/cloudclient" + "google.golang.org/api/option" + "k8s.io/utils/ptr" + "strings" +) + func createGcpNetworksClient(ctx context.Context) (*compute.NetworksClient, error) { c, err := compute.NewNetworksRESTClient(ctx, option.WithCredentialsFile(abstractions.NewOSEnvironment().Get("GCP_SA_JSON_KEY_PATH"))) if err != nil { @@ -48,102 +50,75 @@ type networkClient struct { } type VpcPeeringClient interface { - CreateVpcPeering(ctx context.Context, name *string, remoteVpc *string, remoteProject *string, importCustomRoutes *bool, kymaProject *string, kymaVpc *string) (*pb.NetworkPeering, error) - DeleteVpcPeering(ctx context.Context, name *string, kymaProject *string, kymaVpc *string) (*compute.Operation, error) + DeleteVpcPeering(ctx context.Context, remotePeeringName string, kymaProject string, kymaVpc string) (*compute.Operation, error) + GetVpcPeering(ctx context.Context, remotePeeringName string, project string, vpc string) (*pb.NetworkPeering, error) + CreateRemoteVpcPeering(ctx context.Context, remotePeeringName string, remoteVpc string, remoteProject string, importCustomRoutes bool, kymaProject string, kymaVpc string) (*compute.Operation, error) + CreateKymaVpcPeering(ctx context.Context, remotePeeringName string, remoteVpc string, remoteProject string, importCustomRoutes bool, kymaProject string, kymaVpc string) (*compute.Operation, error) + CheckRemoteNetworkTags(context context.Context, remoteVpc string, remoteProject string, desiredTag string) (bool, error) } -func (c *networkClient) CreateVpcPeering(ctx context.Context, name *string, remoteVpc *string, remoteProject *string, importCustomRoutes *bool, kymaProject *string, kymaVpc *string) (*pb.NetworkPeering, error) { - - kymaNetwork := getFullNetworkUrl(*kymaProject, *kymaVpc) - remoteNetwork := getFullNetworkUrl(*remoteProject, *remoteVpc) - +func CreateVpcPeeringRequest(ctx context.Context, remotePeeringName string, sourceVpc string, sourceProject string, importCustomRoutes bool, exportCustomRoutes bool, destinationProject string, destinationVpc string) (*compute.Operation, error) { gcpNetworkClient, err := createGcpNetworksClient(ctx) - if err != nil { return nil, err } defer gcpNetworkClient.Close() + destinationNetworkUrl := getFullNetworkUrl(destinationProject, destinationVpc) - //NetworkPeering will only be created if the remote vpc has a tag with the kyma shoot name - remoteNetworkInfo, err := gcpNetworkClient.Get(ctx, &pb.GetNetworkRequest{Network: *remoteVpc, Project: *remoteProject}) - if err != nil { - return nil, err - } - - isRemoteNetworkTagged, err := c.CheckRemoteNetworkTags(ctx, remoteNetworkInfo, *kymaVpc) - - if !isRemoteNetworkTagged || (err != nil && err.Error() == "no more items in iterator") { - return nil, fmt.Errorf("remote network " + *remoteVpc + " is not tagged with the kyma shoot name " + *kymaVpc) - } else if err != nil { - return nil, err - } - - //peering from kyma to remote vpc - peeringRequestFromKyma := &pb.AddPeeringNetworkRequest{ - Network: *kymaVpc, - Project: *kymaProject, + vpcPeeringRequest := &pb.AddPeeringNetworkRequest{ + Network: sourceVpc, + Project: sourceProject, NetworksAddPeeringRequestResource: &pb.NetworksAddPeeringRequest{ NetworkPeering: &pb.NetworkPeering{ - Name: name, - Network: &remoteNetwork, - ImportCustomRoutes: importCustomRoutes, + Name: &remotePeeringName, + Network: &destinationNetworkUrl, + ExportCustomRoutes: &exportCustomRoutes, ExchangeSubnetRoutes: ptr.To(true), + ImportCustomRoutes: &importCustomRoutes, }, }, } - _, err = gcpNetworkClient.AddPeering(ctx, peeringRequestFromKyma) + operation, err := gcpNetworkClient.AddPeering(ctx, vpcPeeringRequest) if err != nil { return nil, err } + return operation, nil - var networkPeering *pb.NetworkPeering - net, err := gcpNetworkClient.Get(ctx, &pb.GetNetworkRequest{Network: *kymaVpc, Project: *kymaProject}) - nps := net.GetPeerings() - for _, np := range nps { - if *np.Network == remoteNetwork { - networkPeering = np - break - } - } +} +func (c *networkClient) CreateRemoteVpcPeering(ctx context.Context, remotePeeringName string, remoteVpc string, remoteProject string, customRoutes bool, kymaProject string, kymaVpc string) (*compute.Operation, error) { //peering from remote vpc to kyma //by default exportCustomRoutes is false but if the remote vpc wants kyma to import custom routes, the peering needs to export them :) exportCustomRoutes := false - if *importCustomRoutes { + importCustomRoutes := false + if customRoutes { exportCustomRoutes = true } - peeringRequestFromRemote := &pb.AddPeeringNetworkRequest{ - Network: *remoteVpc, - Project: *remoteProject, - NetworksAddPeeringRequestResource: &pb.NetworksAddPeeringRequest{ - NetworkPeering: &pb.NetworkPeering{ - Name: name, - Network: &kymaNetwork, - ExportCustomRoutes: &exportCustomRoutes, - ExchangeSubnetRoutes: ptr.To(true), - }, - }, - } + return CreateVpcPeeringRequest(ctx, remotePeeringName, remoteVpc, remoteProject, importCustomRoutes, exportCustomRoutes, kymaProject, kymaVpc) +} - _, err = gcpNetworkClient.AddPeering(ctx, peeringRequestFromRemote) - if err != nil { - return networkPeering, err +func (c *networkClient) CreateKymaVpcPeering(ctx context.Context, remotePeeringName string, remoteVpc string, remoteProject string, customRoutes bool, kymaProject string, kymaVpc string) (*compute.Operation, error) { + //peering from kyma to remote vpc + //Kyma will not export custom routes to the remote vpc, but if the remote vpc is exporting them we need to import them + exportCustomRoutes := false + importCustomRoutes := false + if customRoutes { + importCustomRoutes = true } - - return networkPeering, nil + return CreateVpcPeeringRequest(ctx, remotePeeringName, kymaVpc, kymaProject, importCustomRoutes, exportCustomRoutes, remoteProject, remoteVpc) } -func (c *networkClient) DeleteVpcPeering(ctx context.Context, name *string, kymaProject *string, kymaVpc *string) (*compute.Operation, error) { +func (c *networkClient) DeleteVpcPeering(ctx context.Context, remotePeeringName string, kymaProject string, kymaVpc string) (*compute.Operation, error) { gcpNetworkClient, err := createGcpNetworksClient(ctx) if err != nil { return nil, err } defer gcpNetworkClient.Close() deleteVpcPeeringOperation, err := gcpNetworkClient.RemovePeering(ctx, &pb.RemovePeeringNetworkRequest{ - Network: *kymaVpc, - Project: *kymaProject, - NetworksRemovePeeringRequestResource: &pb.NetworksRemovePeeringRequest{Name: name}, + Network: kymaVpc, + Project: kymaProject, + NetworksRemovePeeringRequestResource: &pb.NetworksRemovePeeringRequest{Name: &remotePeeringName}, }) if err != nil { return nil, err @@ -151,11 +126,44 @@ func (c *networkClient) DeleteVpcPeering(ctx context.Context, name *string, kyma return deleteVpcPeeringOperation, nil } +func (c *networkClient) GetVpcPeering(ctx context.Context, remotePeeringName string, project string, vpc string) (*pb.NetworkPeering, error) { + gcpNetworkClient, err := createGcpNetworksClient(ctx) + if err != nil { + return nil, err + } + defer gcpNetworkClient.Close() + network, err := gcpNetworkClient.Get(ctx, &pb.GetNetworkRequest{Network: vpc, Project: project}) + if err != nil { + return nil, err + } + peerings := pie.Filter(network.GetPeerings(), func(peering *pb.NetworkPeering) bool { return peering.GetName() == remotePeeringName }) + + if len(peerings) == 0 { + logger := composed.LoggerFromCtx(ctx) + logger.Info("Vpc Peering not found") + return nil, nil + } + return peerings[0], nil +} + func getFullNetworkUrl(project, vpc string) string { return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s", project, vpc) } -func (c *networkClient) CheckRemoteNetworkTags(context context.Context, remoteNetwork *pb.Network, desiredTag string) (bool, error) { +func (c *networkClient) CheckRemoteNetworkTags(context context.Context, remoteVpc string, remoteProject string, desiredTag string) (bool, error) { + + gcpNetworkClient, err := createGcpNetworksClient(context) + if err != nil { + return false, err + } + defer gcpNetworkClient.Close() + + //NetworkPeering will only be created if the remote vpc has a tag with the kyma shoot name + remoteNetwork, err := gcpNetworkClient.Get(context, &pb.GetNetworkRequest{Network: remoteVpc, Project: remoteProject}) + if err != nil { + return false, err + } + //Unfortunately get networks doesn't return the tags, so we need to use the resource manager tag bindings client tbc, err := resourcemanager.NewTagBindingsClient(context, option.WithCredentialsFile(abstractions.NewOSEnvironment().Get("GCP_SA_JSON_KEY_PATH"))) if err != nil { @@ -168,6 +176,9 @@ func (c *networkClient) CheckRemoteNetworkTags(context context.Context, remoteNe for { tag, err := tagIterator.Next() if err != nil { + if err.Error() == "no more items in iterator" { + return false, nil + } return false, err } //since we are not sure where the user is going to put the tag under, let's check if the tag key contains the desired tag diff --git a/pkg/kcp/provider/gcp/vpcpeering/createKymaVpcPeering.go b/pkg/kcp/provider/gcp/vpcpeering/createKymaVpcPeering.go new file mode 100644 index 000000000..604bebda3 --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/createKymaVpcPeering.go @@ -0,0 +1,93 @@ +/* +required GCP permissions +========================= + - The service account used to create the VPC peering connection needs the following permissions: + ** Creates the VPC peering connection + compute.networks.addPeering => https://cloud.google.com/compute/docs/reference/rest/v1/networks/addPeering + ** Removes the VPC peering connection + compute.networks.removePeering => https://cloud.google.com/compute/docs/reference/rest/v1/networks/removePeering + ** Gets the network (VPCs) in order to retrieve the peerings + compute.networks.get => https://cloud.google.com/compute/docs/reference/rest/v1/networks/get +*/ + +package vpcpeering + +import ( + "context" + "fmt" + cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func createKymaVpcPeering(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.kymaVpcPeering != nil { + return nil, nil + } + + gcpScope := state.Scope().Spec.Scope.Gcp + project := gcpScope.Project + vpc := gcpScope.VpcNetwork + + //First we need to check if the remote VPC is tagged with the shoot name. + isVpcTagged, err := state.client.CheckRemoteNetworkTags(ctx, state.remoteVpc, state.remoteProject, state.Scope().Spec.Scope.Gcp.VpcNetwork) + if err != nil { + logger.Error(err, "Error creating GCP Kyma VPC Peering while checking remote network tags") + return err, ctx + } + + if !isVpcTagged { + logger.Error(err, "Remote network "+state.remoteVpc+" is not tagged with the kyma shoot name "+state.Scope().Spec.Scope.Gcp.VpcNetwork) + return composed.UpdateStatus(state.ObjAsVpcPeering()). + SetExclusiveConditions(metav1.Condition{ + Type: cloudcontrolv1beta1.ConditionTypeError, + Status: "True", + Reason: cloudcontrolv1beta1.ReasonFailedCreatingVpcPeeringConnection, + Message: fmt.Sprintf("Error creating VpcPeering, remote VPC does not have a tag with the key: %s", state.Scope().Spec.Scope.Gcp.VpcNetwork), + }). + ErrorLogMessage("Error creating Remote VpcPeering"). + FailedError(composed.StopWithRequeue). + SuccessError(composed.StopWithRequeueDelay(5*util.Timing.T60000ms())). + Run(ctx, state) + } + + _, err = state.client.CreateKymaVpcPeering( + ctx, + state.remotePeeringName, + state.remoteVpc, + state.remoteProject, + state.importCustomRoutes, + project, + vpc) + + if err != nil { + return composed.UpdateStatus(state.ObjAsVpcPeering()). + SetExclusiveConditions(metav1.Condition{ + Type: cloudcontrolv1beta1.ConditionTypeError, + Status: "True", + Reason: cloudcontrolv1beta1.ReasonFailedCreatingVpcPeeringConnection, + Message: fmt.Sprintf("Error creating Remote VpcPeering %s", err), + }). + ErrorLogMessage("Error creating Remote VpcPeering"). + FailedError(composed.StopWithRequeue). + SuccessError(composed.StopWithRequeueDelay(util.Timing.T60000ms())). + Run(ctx, state) + } + logger.Info("Kyma VPC Peering Connection created") + return composed.StopWithRequeueDelay(3 * util.Timing.T10000ms()), nil +} + +func remoteVpcTagChallenge(ctx context.Context, state *State) (bool, error) { + isVpcTagged, err := state.client.CheckRemoteNetworkTags(ctx, state.remoteVpc, state.remoteProject, state.Scope().Spec.Scope.Gcp.VpcNetwork) + if isVpcTagged == false || (err != nil && err.Error() == "no more items in iterator") { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} diff --git a/pkg/kcp/provider/gcp/vpcpeering/createRemoteVpcPeering.go b/pkg/kcp/provider/gcp/vpcpeering/createRemoteVpcPeering.go new file mode 100644 index 000000000..21ec957bd --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/createRemoteVpcPeering.go @@ -0,0 +1,73 @@ +/* +required GCP permissions +========================= + - The service account used to create the VPC peering connection needs the following permissions: + ** Creates the VPC peering connection + compute.networks.addPeering => https://cloud.google.com/compute/docs/reference/rest/v1/networks/addPeering + ** Gets the network (VPCs) in order to retrieve the peerings + compute.networks.get => https://cloud.google.com/compute/docs/reference/rest/v1/networks/get + ** Fetches the remote network tags + compute.networks.ListEffectiveTags => https://cloud.google.com/resource-manager/reference/rest/v3/tagKeys/get +*/ + +package vpcpeering + +import ( + "context" + "fmt" + cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "regexp" +) + +func createRemoteVpcPeering(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.remoteVpcPeering != nil { + return nil, nil + } + + gcpScope := state.Scope().Spec.Scope.Gcp + project := gcpScope.Project + vpc := gcpScope.VpcNetwork + + _, err := state.client.CreateRemoteVpcPeering( + ctx, + state.remotePeeringName, + state.remoteVpc, + state.remoteProject, + state.importCustomRoutes, + project, + vpc) + + if err != nil { + message := fmt.Sprintf("Error creating Remote VpcPeering %s", err) + // If we already have a peering with the same network and project, we need to let the user know that the peering already exists + // and he might need to either delete the existing peering or use the same name for the new peering. This is required since we don't + // delete any objects on the user project. + matchesExistingPeering, regexError := regexp.Match("There is already a peering (.*) with the same network. Select another network.", []byte(err.Error())) + if regexError != nil { + return err, nil + } + if matchesExistingPeering { + message = fmt.Sprintf("Error creating Remote VpcPeering: %s Please check the VPC peerings on your project.", err) + } + + return composed.UpdateStatus(state.ObjAsVpcPeering()). + SetExclusiveConditions(metav1.Condition{ + Type: cloudcontrolv1beta1.ConditionTypeError, + Status: "True", + Reason: cloudcontrolv1beta1.ReasonFailedCreatingVpcPeeringConnection, + Message: message, + }). + ErrorLogMessage("Error creating Remote VpcPeering"). + FailedError(composed.StopWithRequeue). + SuccessError(composed.StopWithRequeueDelay(util.Timing.T60000ms())). + Run(ctx, state) + } + logger.Info("Remote VPC Peering Connection created") + return composed.StopWithRequeueDelay(3 * util.Timing.T10000ms()), nil +} diff --git a/pkg/kcp/provider/gcp/vpcpeering/createVpcPeering.go b/pkg/kcp/provider/gcp/vpcpeering/createVpcPeering.go deleted file mode 100644 index cc712826b..000000000 --- a/pkg/kcp/provider/gcp/vpcpeering/createVpcPeering.go +++ /dev/null @@ -1,75 +0,0 @@ -package vpcpeering - -import ( - "context" - "fmt" - cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" - "github.com/kyma-project/cloud-manager/pkg/composed" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "time" -) - -func createVpcPeeringConnection(ctx context.Context, st composed.State) (error, context.Context) { - state := st.(*State) - logger := composed.LoggerFromCtx(ctx) - - if state.vpcPeeringConnection != nil { - return nil, nil - } - - gcpScope := state.Scope().Spec.Scope.Gcp - project := gcpScope.Project - vpc := gcpScope.VpcNetwork - - con, err := state.client.CreateVpcPeering( - ctx, - state.peeringName, - state.remoteVpc, - state.remoteProject, - state.importCustomRoutes, - &project, - &vpc) - - if err != nil { - logger.Error(err, "Error creating VPC Peering") - - if err.Error() == "remote network "+*state.remoteVpc+" is not tagged with the kyma shoot name "+vpc { - return composed.UpdateStatus(state.ObjAsVpcPeering()). - SetExclusiveConditions(metav1.Condition{ - Type: cloudcontrolv1beta1.ConditionTypeError, - Status: "True", - Reason: cloudcontrolv1beta1.ReasonFailedLoadingRemoteVpcNetwork, //I believe we should change it for something like ReasonRemoteNetworkNotTagged - Message: fmt.Sprintf("Remote network %s is not tagged with the kyma shoot name %s", *state.remoteVpc, vpc), - }). - ErrorLogMessage("Remote network is not tagged with the kyma shoot name"). - FailedError(composed.StopWithRequeue). - SuccessError(composed.StopWithRequeueDelay(time.Minute)). - Run(ctx, state) - } - - return composed.UpdateStatus(state.ObjAsVpcPeering()). - SetExclusiveConditions(metav1.Condition{ - Type: cloudcontrolv1beta1.ConditionTypeError, - Status: "True", - Reason: cloudcontrolv1beta1.ReasonFailedCreatingVpcPeeringConnection, - Message: fmt.Sprintf("Failed creating VpcPeerings %s", err), - }). - ErrorLogMessage("Error updating VpcPeering status due to failed creating vpc peering connection"). - FailedError(composed.StopWithRequeue). - SuccessError(composed.StopWithRequeueDelay(time.Minute)). - Run(ctx, state) - } - - ctx = composed.LoggerIntoCtx(ctx, logger) - - logger.Info("GCP VPC Peering Connection created") - - state.vpcPeeringConnection = con - - err = state.UpdateObjStatus(ctx) - - if err != nil { - return composed.LogErrorAndReturn(err, "Error updating VPC Peering status", composed.StopWithRequeue, ctx) - } - return nil, ctx -} diff --git a/pkg/kcp/provider/gcp/vpcpeering/deleteVpcPeering.go b/pkg/kcp/provider/gcp/vpcpeering/deleteVpcPeering.go index c90ea995c..89dd5ef7b 100644 --- a/pkg/kcp/provider/gcp/vpcpeering/deleteVpcPeering.go +++ b/pkg/kcp/provider/gcp/vpcpeering/deleteVpcPeering.go @@ -10,17 +10,22 @@ func deleteVpcPeering(ctx context.Context, st composed.State) (error, context.Co obj := state.ObjAsVpcPeering() logger := composed.LoggerFromCtx(ctx) - logger.Info("Deleting GCP VPC Peering " + obj.Spec.VpcPeering.Gcp.PeeringName) + if state.kymaVpcPeering == nil { + logger.Info("VPC Peering is not loaded") + return nil, nil + } + + logger.Info("Deleting GCP VPC Peering " + obj.Spec.VpcPeering.Gcp.RemotePeeringName) _, err := state.client.DeleteVpcPeering( ctx, - &obj.Spec.VpcPeering.Gcp.PeeringName, - &state.Scope().Spec.Scope.Gcp.Project, - &state.Scope().Spec.Scope.Gcp.VpcNetwork, + obj.Spec.VpcPeering.Gcp.RemotePeeringName, + state.Scope().Spec.Scope.Gcp.Project, + state.Scope().Spec.Scope.Gcp.VpcNetwork, ) if err != nil { - return err, ctx + return err, nil } return nil, nil diff --git a/pkg/kcp/provider/gcp/vpcpeering/loadKymaVpcPeering.go b/pkg/kcp/provider/gcp/vpcpeering/loadKymaVpcPeering.go new file mode 100644 index 000000000..ecca97f2c --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/loadKymaVpcPeering.go @@ -0,0 +1,45 @@ +package vpcpeering + +import ( + "context" + "fmt" + "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func loadKymaVpcPeering(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.kymaVpcPeering != nil { + return nil, nil + } + + logger.Info("Loading VPC Peering") + + kymaVpcPeering, err := state.client.GetVpcPeering(ctx, state.remotePeeringName, state.Scope().Spec.Scope.Gcp.Project, state.Scope().Spec.Scope.Gcp.VpcNetwork) + if err != nil { + logger.Error(err, "Error loading Kyma Vpc Peering") + meta.SetStatusCondition(state.ObjAsVpcPeering().Conditions(), metav1.Condition{ + Type: v1beta1.ConditionTypeError, + Status: "True", + Reason: v1beta1.ReasonFailedCreatingVpcPeeringConnection, + Message: fmt.Sprintf("Error loading Kyma Vpc Peering: %s", err), + }) + err = state.UpdateObjStatus(ctx) + if err != nil { + return composed.LogErrorAndReturn(err, + "Error updating status since it was not possible to load the Kyma Vpc Peering", + composed.StopWithRequeueDelay((util.Timing.T10000ms())), + ctx, + ) + } + return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil + } + + state.kymaVpcPeering = kymaVpcPeering + return nil, nil +} diff --git a/pkg/kcp/provider/gcp/vpcpeering/loadRemoteVpcPeering.go b/pkg/kcp/provider/gcp/vpcpeering/loadRemoteVpcPeering.go new file mode 100644 index 000000000..8013dc688 --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/loadRemoteVpcPeering.go @@ -0,0 +1,45 @@ +package vpcpeering + +import ( + "context" + "fmt" + "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func loadRemoteVpcPeering(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.remoteVpcPeering != nil { + return nil, nil + } + + logger.Info("Loading Remote VPC Peering") + + remoteVpcPeering, err := state.client.GetVpcPeering(ctx, state.remotePeeringName, state.remoteProject, state.remoteVpc) + if err != nil { + logger.Error(err, "Error loading Remote VpcPeering") + meta.SetStatusCondition(state.ObjAsVpcPeering().Conditions(), metav1.Condition{ + Type: v1beta1.ConditionTypeError, + Status: "True", + Reason: v1beta1.ReasonFailedCreatingVpcPeeringConnection, + Message: fmt.Sprintf("Error loading Remote Vpc Peering: %s", err), + }) + err = state.UpdateObjStatus(ctx) + if err != nil { + return composed.LogErrorAndReturn(err, + "Error updating status since it was not possible to load the remote Vpc Peering", + composed.StopWithRequeueDelay((util.Timing.T10000ms())), + ctx, + ) + } + return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil + } + + state.remoteVpcPeering = remoteVpcPeering + return nil, nil +} diff --git a/pkg/kcp/provider/gcp/vpcpeering/new.go b/pkg/kcp/provider/gcp/vpcpeering/new.go index 061c098d8..ad9746ad1 100644 --- a/pkg/kcp/provider/gcp/vpcpeering/new.go +++ b/pkg/kcp/provider/gcp/vpcpeering/new.go @@ -3,6 +3,7 @@ package vpcpeering import ( "context" "fmt" + "github.com/kyma-project/cloud-manager/pkg/common/actions" "github.com/kyma-project/cloud-manager/pkg/composed" "github.com/kyma-project/cloud-manager/pkg/kcp/vpcpeering/types" ) @@ -20,25 +21,26 @@ func New(stateFactory StateFactory) composed.Action { return composed.ComposeActions( "gcpVpcPeering", - composed.BuildSwitchAction( - "gcpVpcPeering-switch", - // default action - composed.ComposeActions("gcpVpcPeering-non-delete", - addFinalizer, - createVpcPeeringConnection, - updateSuccessStatus, - composed.StopAndForgetAction, + actions.AddFinalizer, + loadRemoteVpcPeering, + loadKymaVpcPeering, + composed.IfElse(composed.Not(composed.MarkedForDeletionPredicate), + composed.ComposeActions( + "gcpVpcPeering-create", + createRemoteVpcPeering, + waitRemoteVpcPeeringAvailable, + createKymaVpcPeering, + waitVpcPeeringActive, + updateStatus, ), - composed.NewCase( - composed.MarkedForDeletionPredicate, - composed.ComposeActions( - "gcpVpcPeering-delete", - removeReadyCondition, - deleteVpcPeering, - removeFinalizer, - ), + composed.ComposeActions( + "gcpVpcPeering-delete", + removeReadyCondition, + deleteVpcPeering, + waitKymaVpcPeeringDeletion, + actions.RemoveFinalizer, ), - ), // switch + ), composed.StopAndForgetAction, )(ctx, state) } diff --git a/pkg/kcp/provider/gcp/vpcpeering/removeFinalizer.go b/pkg/kcp/provider/gcp/vpcpeering/removeFinalizer.go deleted file mode 100644 index 0d8cbdfae..000000000 --- a/pkg/kcp/provider/gcp/vpcpeering/removeFinalizer.go +++ /dev/null @@ -1,27 +0,0 @@ -package vpcpeering - -import ( - "context" - cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" - "github.com/kyma-project/cloud-manager/pkg/composed" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func removeFinalizer(ctx context.Context, st composed.State) (error, context.Context) { - state := st.(*State) - logger := composed.LoggerFromCtx(ctx) - - isUpdated := controllerutil.RemoveFinalizer(state.ObjAsVpcPeering(), cloudcontrolv1beta1.FinalizerName) - if !isUpdated { - return nil, nil - } - - logger.Info("Removing finalizer") - - err := state.UpdateObj(ctx) - if err != nil { - return composed.LogErrorAndReturn(err, "Error updating KCP VpcPeering after finalizer removed", composed.StopWithRequeue, ctx) - } - - return composed.StopAndForget, nil -} diff --git a/pkg/kcp/provider/gcp/vpcpeering/state.go b/pkg/kcp/provider/gcp/vpcpeering/state.go index 744423c21..518a55d04 100644 --- a/pkg/kcp/provider/gcp/vpcpeering/state.go +++ b/pkg/kcp/provider/gcp/vpcpeering/state.go @@ -1,7 +1,7 @@ package vpcpeering import ( - computepb "cloud.google.com/go/compute/apiv1/computepb" + pb "cloud.google.com/go/compute/apiv1/computepb" "context" "github.com/go-logr/logr" "github.com/kyma-project/cloud-manager/pkg/common/abstractions" @@ -19,11 +19,14 @@ type State struct { //gcp config gcpConfig *gcpclient.GcpConfig - peeringName *string - vpcPeeringConnection *computepb.NetworkPeering - remoteVpc *string - remoteProject *string - importCustomRoutes *bool + remotePeeringName string + remoteVpc string + remoteProject string + importCustomRoutes bool + + //Peerings on both sides + remoteVpcPeering *pb.NetworkPeering + kymaVpcPeering *pb.NetworkPeering } type StateFactory interface { @@ -66,9 +69,9 @@ func newState(vpcPeeringState vpcpeeringtypes.State, client: client, provider: provider, gcpConfig: gcpConfig, - peeringName: &vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.PeeringName, - remoteVpc: &vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.RemoteVpc, - remoteProject: &vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.RemoteProject, - importCustomRoutes: &vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.ImportCustomRoutes, + remotePeeringName: vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.RemotePeeringName, + remoteVpc: vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.RemoteVpc, + remoteProject: vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.RemoteProject, + importCustomRoutes: vpcPeeringState.ObjAsVpcPeering().Spec.VpcPeering.Gcp.ImportCustomRoutes, } } diff --git a/pkg/kcp/provider/gcp/vpcpeering/updateStatus.go b/pkg/kcp/provider/gcp/vpcpeering/updateStatus.go index 0ec990c7d..8fe990b3b 100644 --- a/pkg/kcp/provider/gcp/vpcpeering/updateStatus.go +++ b/pkg/kcp/provider/gcp/vpcpeering/updateStatus.go @@ -8,8 +8,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func updateSuccessStatus(ctx context.Context, st composed.State) (error, context.Context) { +func updateStatus(ctx context.Context, st composed.State) (error, context.Context) { state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + logger.Info("GCP VPC Peering Update Status") + + if composed.MarkedForDeletionPredicate(ctx, state) { + logger.Info("GCP VPC Peering is marked for deletion") + return nil, nil + } if meta.IsStatusConditionTrue( *state.ObjAsVpcPeering().Conditions(), @@ -23,7 +31,7 @@ func updateSuccessStatus(ctx context.Context, st composed.State) (error, context Type: cloudcontrol1beta1.ConditionTypeReady, Status: "True", Reason: cloudcontrol1beta1.ReasonReady, - Message: "Additional VpcPeerings(s) are provisioned", + Message: "VpcPeering :" + state.remotePeeringName + " is provisioned", }). ErrorLogMessage("Error updating VpcPeering success status after setting Ready condition"). SuccessLogMsg("KPC VpcPeering is ready"). diff --git a/pkg/kcp/provider/gcp/vpcpeering/waitKymaVpcPeeringDeletion.go b/pkg/kcp/provider/gcp/vpcpeering/waitKymaVpcPeeringDeletion.go new file mode 100644 index 000000000..f6bf1920a --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/waitKymaVpcPeeringDeletion.go @@ -0,0 +1,19 @@ +package vpcpeering + +import ( + "context" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" +) + +func waitKymaVpcPeeringDeletion(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.kymaVpcPeering != nil { + logger.Info("GCP Kyma VPC Peering is not deleted yet, re-queueing with delay") + return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil + } + + return nil, nil +} diff --git a/pkg/kcp/provider/gcp/vpcpeering/waitRemoteVpcPeeringAvailable.go b/pkg/kcp/provider/gcp/vpcpeering/waitRemoteVpcPeeringAvailable.go new file mode 100644 index 000000000..4771cc8f3 --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/waitRemoteVpcPeeringAvailable.go @@ -0,0 +1,20 @@ +package vpcpeering + +import ( + pb "cloud.google.com/go/compute/apiv1/computepb" + "context" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" +) + +func waitRemoteVpcPeeringAvailable(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if *state.remoteVpcPeering.State != pb.NetworkPeering_INACTIVE.String() && *state.remoteVpcPeering.State != pb.NetworkPeering_ACTIVE.String() { + logger.Info("GCP Remote VPC Peering is not ready yet, re-queueing with delay") + return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil + } + + return nil, nil +} diff --git a/pkg/kcp/provider/gcp/vpcpeering/waitVpcPeeringActive.go b/pkg/kcp/provider/gcp/vpcpeering/waitVpcPeeringActive.go new file mode 100644 index 000000000..593388384 --- /dev/null +++ b/pkg/kcp/provider/gcp/vpcpeering/waitVpcPeeringActive.go @@ -0,0 +1,20 @@ +package vpcpeering + +import ( + pb "cloud.google.com/go/compute/apiv1/computepb" + "context" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" +) + +func waitVpcPeeringActive(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.kymaVpcPeering.GetState() != pb.NetworkPeering_ACTIVE.String() && state.remoteVpcPeering.GetState() != pb.NetworkPeering_ACTIVE.String() { + logger.Info("GCP VPC Peering is not ready yet, re-queueing with delay") + return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil + } + + return nil, nil +} diff --git a/pkg/skr/gcpvpcpeering/createKcpVpcPeering.go b/pkg/skr/gcpvpcpeering/createKcpVpcPeering.go index 098323e4b..aa40091dd 100644 --- a/pkg/skr/gcpvpcpeering/createKcpVpcPeering.go +++ b/pkg/skr/gcpvpcpeering/createKcpVpcPeering.go @@ -22,9 +22,9 @@ func createKcpVpcPeering(ctx context.Context, st composed.State) (error, context obj := state.ObjAsGcpVpcPeering() //If the peering name is not set, use the object name - if obj.Spec.PeeringName == "" { - obj.Spec.PeeringName = obj.Name - } + //if obj.Spec.RemotePeeringName == "" { + // obj.Spec.RemotePeeringName = obj.Name + //} state.KcpVpcPeering = &cloudcontrolv1beta1.VpcPeering{ ObjectMeta: metav1.ObjectMeta{ @@ -49,7 +49,7 @@ func createKcpVpcPeering(ctx context.Context, st composed.State) (error, context ImportCustomRoutes: obj.Spec.ImportCustomRoutes, RemoteVpc: obj.Spec.RemoteVpc, RemoteProject: obj.Spec.RemoteProject, - PeeringName: obj.Spec.PeeringName, + RemotePeeringName: obj.Spec.RemotePeeringName, }, }, }, diff --git a/pkg/skr/gcpvpcpeering/reconciler.go b/pkg/skr/gcpvpcpeering/reconciler.go index 0487e05a8..acc1803c1 100644 --- a/pkg/skr/gcpvpcpeering/reconciler.go +++ b/pkg/skr/gcpvpcpeering/reconciler.go @@ -2,6 +2,7 @@ package gcpvpcpeering import ( "context" + "github.com/kyma-project/cloud-manager/pkg/common/actions" "github.com/kyma-project/cloud-manager/pkg/composed" skrruntime "github.com/kyma-project/cloud-manager/pkg/skr/runtime" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -39,12 +40,22 @@ func (r *reconciler) newAction() composed.Action { return composed.ComposeActions( "crGcpVpcPeeringMain", composed.LoadObj, - addFinalizer, loadKcpGcpVpcPeering, - createKcpVpcPeering, - deleteKcpVpcPeering, - removeFinalizer, - updateStatus, + composed.IfElse(composed.Not(composed.MarkedForDeletionPredicate), + composed.ComposeActions( + "gcpVpcPeering-create", + actions.AddFinalizer, + createKcpVpcPeering, + waitKcpStatusUpdate, + updateStatus, + waitSkrStatusReady, + ), + composed.ComposeActions( + "gcpVpcPeering-delete", + deleteKcpVpcPeering, + actions.RemoveFinalizer, + ), + ), composed.StopAndForgetAction, ) } diff --git a/pkg/skr/gcpvpcpeering/waitKcpStatusUpdate.go b/pkg/skr/gcpvpcpeering/waitKcpStatusUpdate.go new file mode 100644 index 000000000..60d885604 --- /dev/null +++ b/pkg/skr/gcpvpcpeering/waitKcpStatusUpdate.go @@ -0,0 +1,17 @@ +package gcpvpcpeering + +import ( + "context" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" +) + +func waitKcpStatusUpdate(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + + if len(state.KcpVpcPeering.Status.Conditions) == 0 { + return composed.StopWithRequeueDelay(2 * util.Timing.T100ms()), nil + } + + return nil, nil +} diff --git a/pkg/skr/gcpvpcpeering/waitSkrStatusReady.go b/pkg/skr/gcpvpcpeering/waitSkrStatusReady.go new file mode 100644 index 000000000..edbcdb7fe --- /dev/null +++ b/pkg/skr/gcpvpcpeering/waitSkrStatusReady.go @@ -0,0 +1,18 @@ +package gcpvpcpeering + +import ( + "context" + cloudresourcesv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-resources/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/composed" + "github.com/kyma-project/cloud-manager/pkg/util" +) + +func waitSkrStatusReady(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + + if state.ObjAsGcpVpcPeering().Status.Conditions[0].Status != cloudresourcesv1beta1.StateReady { + return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil + } + + return nil, nil +} diff --git a/pkg/testinfra/dsl/kcpVpcPeering.go b/pkg/testinfra/dsl/kcpVpcPeering.go index e0c824925..6d8507065 100644 --- a/pkg/testinfra/dsl/kcpVpcPeering.go +++ b/pkg/testinfra/dsl/kcpVpcPeering.go @@ -88,7 +88,7 @@ func WithKcpVpcPeeringSpecAzure(allowVnetAccess bool, remoteVnet, remoteResource } } -func WithKcpVpcPeeringSpecGCP(remoteVpc, remoteProject, peeringName string, importCustomRoutes bool) ObjAction { +func WithKcpVpcPeeringSpecGCP(remoteVpc, remoteProject, remotePeeringName string, importCustomRoutes bool) ObjAction { return &objAction{ f: func(obj client.Object) { x := obj.(*cloudcontrolv1beta1.VpcPeering) @@ -96,7 +96,7 @@ func WithKcpVpcPeeringSpecGCP(remoteVpc, remoteProject, peeringName string, impo x.Spec.VpcPeering.Gcp = &cloudcontrolv1beta1.GcpVpcPeering{ RemoteVpc: remoteVpc, RemoteProject: remoteProject, - PeeringName: peeringName, + RemotePeeringName: remotePeeringName, ImportCustomRoutes: importCustomRoutes, } } diff --git a/tools/demo/gcpvpcpeering/kcp-gcp-vpcpeering.yaml b/tools/demo/gcpvpcpeering/kcp-gcp-vpcpeering.yaml index 90da51b23..c95e6ae9c 100644 --- a/tools/demo/gcpvpcpeering/kcp-gcp-vpcpeering.yaml +++ b/tools/demo/gcpvpcpeering/kcp-gcp-vpcpeering.yaml @@ -6,7 +6,7 @@ metadata: spec: vpcPeering: gcp: - peeringName: vpcpeering-sap-gcp-skr-dev-cust-00002-sap-sc-learn + remotePeeringName: vpcpeering-sap-gcp-skr-dev-cust-00002-sap-sc-learn remoteProject: sap-sc-learn remoteVpc: default importCustomRoutes: false diff --git a/tools/demo/gcpvpcpeering/skr-gcp-vpcpeering.yaml b/tools/demo/gcpvpcpeering/skr-gcp-vpcpeering.yaml index 69a93d1bd..a48c772aa 100644 --- a/tools/demo/gcpvpcpeering/skr-gcp-vpcpeering.yaml +++ b/tools/demo/gcpvpcpeering/skr-gcp-vpcpeering.yaml @@ -6,7 +6,7 @@ metadata: name: "vpcpeering-test" namespace: "kube-public" spec: - peeringName: "vpcpeering-sap-gcp-skr-dev-cust-00002-sap-sc-learn" + remotePeeringName: "vpcpeering-sap-gcp-skr-dev-cust-00002-sap-sc-learn" remoteProject: "sap-sc-learn" remoteVpc: "default" # Default is false.