Skip to content

Commit

Permalink
feat(GCP VpcPeering) updating peering to the new network implementati…
Browse files Browse the repository at this point in the history
  • Loading branch information
bru-jer-work authored Oct 3, 2024
1 parent 31dca64 commit 3d9d2e2
Show file tree
Hide file tree
Showing 30 changed files with 664 additions and 93 deletions.
18 changes: 12 additions & 6 deletions api/cloud-resources/v1beta1/gcpvpcpeering_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,24 @@ import (
// Important: Run "make" to regenerate code after modifying this file

type GcpVpcPeeringSpec struct {
// +kubebuilder:validation:Required
// +kubebuilder:validation:Optional
// +kubebuilder:default:=false
// +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."
// +kubebuilder:validation:XValidation:rule=(size(self) <= 63 && size(self) >= 1), message="RemotePeeringName should be at least 1 character, with a maximum of 63 characters."
// +kubebuilder:validation:XValidation:rule=(self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != ''), message="RemotePeeringName must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens."
RemotePeeringName string `json:"remotePeeringName,omitempty"`
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemoteVpc is immutable."
// +kubebuilder:validation:XValidation:rule=(size(self) <= 63 && size(self) >= 1), message="RemoteVpc should be at least 1 character, with a maximum of 63 characters."
// +kubebuilder:validation:XValidation:rule=(self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != ''), message="RemoteVpc must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens."
RemoteVpc string `json:"remoteVpc,omitempty"`
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemoteNetwork is immutable."
// +kubebuilder:validation:XValidation:rule=(size(self) <= 30 && size(self) >= 6), message="RemoteProject must be 6 to 30 characters in length."
// +kubebuilder:validation:XValidation:rule=(self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != ''), message="RemoteProject must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens."
// +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="RemoteProject is immutable."
RemoteProject string `json:"remoteProject,omitempty"`
}

Expand All @@ -33,10 +40,9 @@ type GcpVpcPeeringStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:scope=Cluster,categories={kyma-cloud-manager}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,categories={kyma-cloud-manager}
type GcpVpcPeering struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ spec:
spec:
properties:
importCustomRoutes:
default: false
type: boolean
x-kubernetes-validations:
- message: ImportCustomRoutes is immutable.
Expand All @@ -50,16 +51,28 @@ spec:
x-kubernetes-validations:
- message: RemotePeeringName is immutable.
rule: (self == oldSelf)
- message: RemotePeeringName should be at least 1 character, with a maximum of 63 characters.
rule: (size(self) <= 63 && size(self) >= 1)
- message: RemotePeeringName must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens.
rule: (self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != '')
remoteProject:
type: string
x-kubernetes-validations:
- message: RemoteNetwork is immutable.
- message: RemoteProject must be 6 to 30 characters in length.
rule: (size(self) <= 30 && size(self) >= 6)
- message: RemoteProject must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens.
rule: (self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != '')
- message: RemoteProject is immutable.
rule: (self == oldSelf)
remoteVpc:
type: string
x-kubernetes-validations:
- message: RemoteVpc is immutable.
rule: (self == oldSelf)
- message: RemoteVpc should be at least 1 character, with a maximum of 63 characters.
rule: (size(self) <= 63 && size(self) >= 1)
- message: RemoteVpc must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens.
rule: (self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != '')
type: object
status:
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ spec:
spec:
properties:
importCustomRoutes:
default: false
type: boolean
x-kubernetes-validations:
- message: ImportCustomRoutes is immutable.
Expand All @@ -50,16 +51,28 @@ spec:
x-kubernetes-validations:
- message: RemotePeeringName is immutable.
rule: (self == oldSelf)
- message: RemotePeeringName should be at least 1 character, with a maximum of 63 characters.
rule: (size(self) <= 63 && size(self) >= 1)
- message: RemotePeeringName must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens.
rule: (self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != '')
remoteProject:
type: string
x-kubernetes-validations:
- message: RemoteNetwork is immutable.
- message: RemoteProject must be 6 to 30 characters in length.
rule: (size(self) <= 30 && size(self) >= 6)
- message: RemoteProject must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens.
rule: (self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != '')
- message: RemoteProject is immutable.
rule: (self == oldSelf)
remoteVpc:
type: string
x-kubernetes-validations:
- message: RemoteVpc is immutable.
rule: (self == oldSelf)
- message: RemoteVpc should be at least 1 character, with a maximum of 63 characters.
rule: (size(self) <= 63 && size(self) >= 1)
- message: RemoteVpc must start with a lowercase letter, end with a lowercase letter or number, and only contain lowercase letters, numbers, and hyphens.
rule: (self.find('^[a-z]([-a-z0-9]*[a-z0-9])?$') != '')
type: object
status:
properties:
Expand Down
143 changes: 133 additions & 10 deletions internal/controller/cloud-control/vpcpeering_gcp_test.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,177 @@
package cloudcontrol

import (
pb "cloud.google.com/go/compute/apiv1/computepb"
cloudcontrolv1beta1 "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1"
networkPkg "github.com/kyma-project/cloud-manager/pkg/kcp/network"
scopePkg "github.com/kyma-project/cloud-manager/pkg/kcp/scope"
. "github.com/kyma-project/cloud-manager/pkg/testinfra/dsl"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
)

var _ = Describe("Feature: KCP VpcPeering", func() {
It("Scenario: KCP GCP VpcPeering is created", func() {
const (
kymaName = "57bc9639-d752-4f67-8b9e-7cd12514575f"
kymaName = "7e829442-f92e-4205-9d36-0d622a422d74"
kymaNetworkName = kymaName + "--kyma"
kymaProject = "kyma-project"
kymaVpc = "shoot-12345-abc"
remoteNetworkName = "f5331c29-bb1a-439c-8376-94be50232eb4"
remotePeeringName = "peering-sap-gcp-skr-dev-cust-00002-to-sap-sc-learn"
remoteVpc = "default"
remoteProject = "sap-sc-learn"
remoteRefNamespace = "kcp-system"
remoteRefName = "skr-gcp-vpcpeering"
importCustomRoutes = true
importCustomRoutes = false
)

scope := &cloudcontrolv1beta1.Scope{}

By("Given Scope exists", func() {
// Tell Scope reconciler to ignore this kymaName
scopePkg.Ignore.AddName(kymaName)

Eventually(CreateScopeGcp).
WithArguments(infra.Ctx(), infra, scope, WithName(kymaName)).
Should(Succeed())
})

vpcpeering := &cloudcontrolv1beta1.VpcPeering{}
// and Given the Kyma network object exists in KCP
kymaNetwork := &cloudcontrolv1beta1.Network{
Spec: cloudcontrolv1beta1.NetworkSpec{
Network: cloudcontrolv1beta1.NetworkInfo{
Reference: &cloudcontrolv1beta1.NetworkReference{
Gcp: &cloudcontrolv1beta1.GcpNetworkReference{
GcpProject: kymaProject,
NetworkName: kymaVpc,
},
},
},
Type: cloudcontrolv1beta1.NetworkTypeKyma,
},
}

By("And Given Kyma Network exists in KCP", func() {
// Tell Scope reconciler to ignore this kymaName
networkPkg.Ignore.AddName(kymaNetworkName)

Eventually(CreateObj).
WithArguments(infra.Ctx(), infra.KCP().Client(), kymaNetwork, WithName(kymaNetworkName), WithScope(scope.Name)).
Should(Succeed())
})

// and Given the remote network object exists in KCP
remoteNetwork := &cloudcontrolv1beta1.Network{
Spec: cloudcontrolv1beta1.NetworkSpec{
Network: cloudcontrolv1beta1.NetworkInfo{
Reference: &cloudcontrolv1beta1.NetworkReference{
Gcp: &cloudcontrolv1beta1.GcpNetworkReference{
GcpProject: remoteProject,
NetworkName: remoteVpc,
},
},
},
Type: cloudcontrolv1beta1.NetworkTypeExternal,
},
}

By("And Given Remote Network exists in KCP", func() {
// Tell Scope reconciler to ignore this kymaName
networkPkg.Ignore.AddName(remoteNetworkName)

Eventually(CreateObj).
WithArguments(infra.Ctx(), infra.KCP().Client(), remoteNetwork, WithName(remoteNetworkName), WithScope(scope.Name), WithState("Ready")).
Should(Succeed())
})

By("When KCP KymaNetwork is Ready", func() {
Eventually(UpdateStatus).
WithArguments(infra.Ctx(),
infra.KCP().Client(),
kymaNetwork,
WithState("Ready"),
WithConditions(KcpReadyCondition())).
Should(Succeed())
})

By("And When KCP RemoteNetwork is Ready", func() {
Eventually(UpdateStatus).
WithArguments(infra.Ctx(),
infra.KCP().Client(),
remoteNetwork,
WithState("Ready"),
WithConditions(KcpReadyCondition())).
Should(Succeed())
})

vpcpeering := &cloudcontrolv1beta1.VpcPeering{
Spec: cloudcontrolv1beta1.VpcPeeringSpec{
Details: &cloudcontrolv1beta1.VpcPeeringDetails{
LocalNetwork: klog.ObjectRef{
Name: kymaNetwork.Name,
Namespace: kymaNetwork.Namespace,
},
RemoteNetwork: klog.ObjectRef{
Name: remoteNetwork.Name,
Namespace: remoteNetwork.Namespace,
},
PeeringName: remotePeeringName,
LocalPeeringName: "cm-" + remoteNetworkName,
ImportCustomRoutes: importCustomRoutes,
},
},
}

By("When KCP VpcPeering is created", func() {
Eventually(CreateKcpVpcPeering).
By("And When KCP VpcPeering is created and the remote network is tagged", func() {
Eventually(CreateObj).
WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering,
WithName(remotePeeringName),
WithKcpVpcPeeringRemoteRef(remoteRefNamespace, remoteRefName),
WithName(remoteNetworkName),
WithRemoteRef(remoteRefName),
WithScope(kymaName),
WithKcpVpcPeeringSpecGCP(remoteVpc, remoteProject, remotePeeringName, importCustomRoutes),
).
Should(Succeed())
})

var remotePeeringObject *pb.NetworkPeering
By("Then GCP VpcPeering is created on remote side", func() {
Eventually(LoadAndCheck).
WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering,
NewObjActions(),
HavingVpcPeeringStatusRemoteId(),
).
Should(Succeed())
remotePeeringObject = infra.GcpMock().GetMockVpcPeering(remoteProject, remoteVpc)
})

var kymaPeeringObject *pb.NetworkPeering
By("And Then GCP VpcPeering is created on kyma side", func() {
Eventually(LoadAndCheck).
WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering,
NewObjActions(),
HavingVpcPeeringStatusId(),
).
Should(Succeed())
kymaPeeringObject = infra.GcpMock().GetMockVpcPeering(kymaProject, kymaVpc)
})

By("When GCP VpcPeering is active on the remote side", func() {
// GCP will set both to ACTIVE when the peering is active
infra.GcpMock().SetMockVpcPeeringLifeCycleState(remoteProject, remoteVpc, pb.NetworkPeering_ACTIVE)
Expect(ptr.Deref(remotePeeringObject.State, "") == pb.NetworkPeering_ACTIVE.String()).Should(BeTrue())
})

By("And When GCP VpcPeering is active on the kyma side", func() {
// GCP will set both to ACTIVE when the peering is active
infra.GcpMock().SetMockVpcPeeringLifeCycleState(kymaProject, kymaVpc, pb.NetworkPeering_ACTIVE)
Expect(ptr.Deref(kymaPeeringObject.State, "") == pb.NetworkPeering_ACTIVE.String()).Should(BeTrue())
})

By("Then KCP VpcPeering has Ready condition", func() {
Eventually(LoadAndCheck).
WithArguments(infra.Ctx(), infra.KCP().Client(), vpcpeering,
NewObjActions(),
HaveFinalizer(cloudcontrolv1beta1.FinalizerName),
HavingConditionTrue(cloudcontrolv1beta1.ConditionTypeReady),
).
Should(Succeed())
Expand Down
2 changes: 2 additions & 0 deletions pkg/kcp/provider/gcp/mock/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ type Server interface {
ClientErrors

MemoryStoreClientFakeUtils

VpcPeeringMockClientUtils
}
29 changes: 23 additions & 6 deletions pkg/kcp/provider/gcp/mock/vpcPeeringStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ type vpcPeeringStore struct {
items map[string]*vpcPeeringEntry
}

type VpcPeeringMockClientUtils interface {
GetMockVpcPeering(project string, vpc string) *pb.NetworkPeering
SetMockVpcPeeringLifeCycleState(project string, vpc string, state pb.NetworkPeering_State)
}

func getFullNetworkUrl(project, vpc string) string {
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s", project, vpc)
}
Expand All @@ -37,18 +42,16 @@ func (s *vpcPeeringStore) CreateRemoteVpcPeering(ctx context.Context, remotePeer
return nil
}

state := pb.NetworkPeering_ACTIVE.String()

item := &vpcPeeringEntry{
peering: &pb.NetworkPeering{
Name: &remotePeeringName,
Network: &kymaNetwork,
ImportCustomRoutes: &importCustomRoutes,
ExportCustomRoutes: &exportCustomRoutes,
ExchangeSubnetRoutes: ptr.To(true),
State: ptr.To(pb.NetworkPeering_INACTIVE.String()),
},
}
item.peering.State = &state
s.items[remoteNetwork] = item

return nil
Expand All @@ -72,18 +75,16 @@ func (s *vpcPeeringStore) CreateKymaVpcPeering(ctx context.Context, remotePeerin
return nil
}

state := pb.NetworkPeering_ACTIVE.String()

item := &vpcPeeringEntry{
peering: &pb.NetworkPeering{
Name: &remotePeeringName,
Network: &remoteNetwork,
ImportCustomRoutes: &importCustomRoutes,
ExportCustomRoutes: &exportCustomRoutes,
ExchangeSubnetRoutes: ptr.To(true),
State: ptr.To(pb.NetworkPeering_INACTIVE.String()),
},
}
item.peering.State = &state

s.items[kymaNetwork] = item

Expand Down Expand Up @@ -127,3 +128,19 @@ func (s *vpcPeeringStore) DeleteVpcPeering(ctx context.Context, remotePeeringNam
delete(s.items, kymaNetwork)
return nil
}

// Fake client implementations to mimic google API calls
func (s *vpcPeeringStore) SetMockVpcPeeringLifeCycleState(project string, vpc string, state pb.NetworkPeering_State) {
stateString := state.String()
if s.items[getFullNetworkUrl(project, vpc)] != nil {
s.items[getFullNetworkUrl(project, vpc)].peering.State = &stateString
}
}

func (s *vpcPeeringStore) GetMockVpcPeering(project string, vpc string) *pb.NetworkPeering {
_, peeringExists := s.items[getFullNetworkUrl(project, vpc)]
if !peeringExists {
return nil
}
return s.items[getFullNetworkUrl(project, vpc)].peering
}
2 changes: 1 addition & 1 deletion pkg/kcp/provider/gcp/vpcpeering/client/vpcPeeringClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (c *networkClient) CheckRemoteNetworkTags(context context.Context, remoteVp
}
//ListEffectiveTags requires the networkId instead of name therefore we need to convert the selfLinkId to the format that the tag bindings client expects
//more info here: https://cloud.google.com/iam/docs/full-resource-names
tagIterator := tbc.ListEffectiveTags(context, &resourcemanagerpb.ListEffectiveTagsRequest{Parent: strings.Replace(*remoteNetwork.SelfLinkWithId, "https://www.googleapis.com/compute/v1", "//compute.googleapis.com", 1)})
tagIterator := tbc.ListEffectiveTags(context, &resourcemanagerpb.ListEffectiveTagsRequest{Parent: strings.Replace(ptr.Deref(remoteNetwork.SelfLinkWithId, ""), "https://www.googleapis.com/compute/v1", "//compute.googleapis.com", 1)})
defer func(tbc *resourcemanager.TagBindingsClient) {
err := tbc.Close()
if err != nil {
Expand Down
Loading

0 comments on commit 3d9d2e2

Please sign in to comment.