diff --git a/api/v1beta3/cloudstackfailuredomain_types.go b/api/v1beta3/cloudstackfailuredomain_types.go index 4f3f2cda..a7f27657 100644 --- a/api/v1beta3/cloudstackfailuredomain_types.go +++ b/api/v1beta3/cloudstackfailuredomain_types.go @@ -62,6 +62,13 @@ type Network struct { // +optional Netmask string `json:"netmask,omitempty"` + // Cloudstack Network Offering the cluster is built in. + // Default is "DefaultIsolatedNetworkOfferingWithSourceNatService" for + // isolated networks and "DefaultIsolatedNetworkOfferingForVpcNetworks" + // for VPC networks. + // +optional + Offering string `json:"offering,omitempty"` + // Cloudstack VPC the network belongs to. // +optional VPC *VPC `json:"vpc,omitempty"` @@ -79,6 +86,11 @@ type VPC struct { // CIDR for the VPC. // +optional CIDR string `json:"cidr,omitempty"` + + // Cloudstack VPC Offering for the network. + // Default is "Default VPC offering" + // +optional + Offering string `json:"offering,omitempty"` } // CloudStackZoneSpec specifies a Zone's details. diff --git a/api/v1beta3/cloudstackisolatednetwork_types.go b/api/v1beta3/cloudstackisolatednetwork_types.go index 76b2a1f5..f2253f6e 100644 --- a/api/v1beta3/cloudstackisolatednetwork_types.go +++ b/api/v1beta3/cloudstackisolatednetwork_types.go @@ -48,6 +48,13 @@ type CloudStackIsolatedNetworkSpec struct { // +optional Netmask string `json:"netmask,omitempty"` + // Offering for the network. + // Default is "DefaultIsolatedNetworkOfferingWithSourceNatService" for + // isolated networks and "DefaultIsolatedNetworkOfferingForVpcNetworks" + // for VPC networks. + // +optional + Offering string `json:"offering,omitempty"` + // VPC the network belongs to. // +optional VPC *VPC `json:"vpc,omitempty"` @@ -67,12 +74,13 @@ type CloudStackIsolatedNetworkStatus struct { func (n *CloudStackIsolatedNetwork) Network() *Network { return &Network{ - Name: n.Spec.Name, - Type: "IsolatedNetwork", - ID: n.Spec.ID, - Gateway: n.Spec.Gateway, - Netmask: n.Spec.Netmask, - VPC: n.Spec.VPC, + Name: n.Spec.Name, + Type: "IsolatedNetwork", + ID: n.Spec.ID, + Gateway: n.Spec.Gateway, + Netmask: n.Spec.Netmask, + VPC: n.Spec.VPC, + Offering: n.Spec.Offering, } } diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml index 9839d0fe..5d8c625a 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml @@ -407,6 +407,12 @@ spec: description: Cloudstack Network Netmask the cluster is built in. type: string + offering: + description: Cloudstack Network Offering the cluster + is built in. Default is "DefaultIsolatedNetworkOfferingWithSourceNatService" + for isolated networks and "DefaultIsolatedNetworkOfferingForVpcNetworks" + for VPC networks. + type: string type: description: Cloudstack Network Type the cluster is built in. @@ -423,6 +429,10 @@ spec: name: description: Cloudstack VPC Name of the network. type: string + offering: + description: Cloudstack VPC Offering for the network. + Default is "Default VPC offering" + type: string type: object required: - name diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml index 936b10f7..f66467cd 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml @@ -184,6 +184,12 @@ spec: description: Cloudstack Network Netmask the cluster is built in. type: string + offering: + description: Cloudstack Network Offering the cluster is built + in. Default is "DefaultIsolatedNetworkOfferingWithSourceNatService" + for isolated networks and "DefaultIsolatedNetworkOfferingForVpcNetworks" + for VPC networks. + type: string type: description: Cloudstack Network Type the cluster is built in. @@ -200,6 +206,10 @@ spec: name: description: Cloudstack VPC Name of the network. type: string + offering: + description: Cloudstack VPC Offering for the network. + Default is "Default VPC offering" + type: string type: object required: - name diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackisolatednetworks.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackisolatednetworks.yaml index 9c24ddc1..3e74ea3e 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackisolatednetworks.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackisolatednetworks.yaml @@ -204,6 +204,11 @@ spec: netmask: description: Netmask for the network. type: string + offering: + description: Offering for the network. Default is "DefaultIsolatedNetworkOfferingWithSourceNatService" + for isolated networks and "DefaultIsolatedNetworkOfferingForVpcNetworks" + for VPC networks. + type: string vpc: description: VPC the network belongs to. properties: @@ -216,6 +221,10 @@ spec: name: description: Cloudstack VPC Name of the network. type: string + offering: + description: Cloudstack VPC Offering for the network. Default + is "Default VPC offering" + type: string type: object required: - controlPlaneEndpoint diff --git a/controllers/utils/isolated_network.go b/controllers/utils/isolated_network.go index 83e20000..05073c6a 100644 --- a/controllers/utils/isolated_network.go +++ b/controllers/utils/isolated_network.go @@ -45,6 +45,7 @@ func (r *ReconciliationRunner) GenerateIsolatedNetwork(name string, fdNameFunc f csIsoNet.Spec.ControlPlaneEndpoint.Port = r.CSCluster.Spec.ControlPlaneEndpoint.Port csIsoNet.Spec.Gateway = network.Gateway csIsoNet.Spec.Netmask = network.Netmask + csIsoNet.Spec.Offering = network.Offering if network.VPC != nil { csIsoNet.Spec.VPC = network.VPC diff --git a/pkg/cloud/isolated_network.go b/pkg/cloud/isolated_network.go index 8d82a815..5bbc28af 100644 --- a/pkg/cloud/isolated_network.go +++ b/pkg/cloud/isolated_network.go @@ -112,6 +112,10 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN offeringName = NetVPCOffering } + if isoNet.Spec.Offering != "" { + offeringName = isoNet.Spec.Offering + } + // Get network offering ID. offeringID, err := c.getOfferingID(offeringName) if err != nil { @@ -153,6 +157,19 @@ func (c *client) OpenFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwork) (r if isoNet.Spec.VPC != nil && isoNet.Spec.VPC.ID != "" { return nil } + + // If network's egress policy is true, then we don't need to open the firewall rules for all protocols + network, count, err := c.cs.Network.GetNetworkByID(isoNet.Spec.ID, cloudstack.WithProject(c.user.Project.ID)) + if err != nil { + return errors.Wrapf(err, "failed to get network by ID %s", isoNet.Spec.ID) + } + if count == 0 { + return errors.Errorf("no network found with ID %s", isoNet.Spec.ID) + } + if network.Egressdefaultpolicy { + return nil + } + protocols := []string{NetworkProtocolTCP, NetworkProtocolUDP, NetworkProtocolICMP} for _, proto := range protocols { p := c.cs.Firewall.NewCreateEgressFirewallRuleParams(isoNet.Spec.ID, proto) diff --git a/pkg/cloud/isolated_network_test.go b/pkg/cloud/isolated_network_test.go index 38e09137..41ffd10a 100644 --- a/pkg/cloud/isolated_network_test.go +++ b/pkg/cloud/isolated_network_test.go @@ -84,6 +84,7 @@ var _ = ginkgo.Describe("Network", func() { PublicIpAddresses: []*csapi.PublicIpAddress{{Id: dummies.PublicIPID, Ipaddress: "fakeIP"}}}, nil) as.EXPECT().NewAssociateIpAddressParams().Return(&csapi.AssociateIpAddressParams{}) as.EXPECT().AssociateIpAddress(gomock.Any()) + ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(&csapi.Network{Egressdefaultpolicy: false}, 1, nil) fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(_ string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} @@ -138,6 +139,7 @@ var _ = ginkgo.Describe("Network", func() { ginkgo.Context("for a closed firewall", func() { ginkgo.It("OpenFirewallRule asks CloudStack to open the firewall", func() { dummies.Zone1.Network = dummies.ISONet1 + ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(&csapi.Network{Egressdefaultpolicy: false}, 1, nil) fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(_ string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} @@ -165,6 +167,7 @@ var _ = ginkgo.Describe("Network", func() { ginkgo.It("OpenFirewallRule asks CloudStack to open the firewall anyway, but doesn't fail", func() { dummies.Zone1.Network = dummies.ISONet1 + ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(&csapi.Network{Egressdefaultpolicy: false}, 1, nil) fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(_ string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} diff --git a/pkg/cloud/network.go b/pkg/cloud/network.go index ec29203c..347b3efc 100644 --- a/pkg/cloud/network.go +++ b/pkg/cloud/network.go @@ -65,6 +65,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) { net.Type = netDetails.Type net.Gateway = netDetails.Gateway net.Netmask = netDetails.Netmask + net.Offering = netDetails.Networkofferingname if netDetails.Vpcid != "" { if net.VPC == nil { net.VPC = &infrav1.VPC{} @@ -88,6 +89,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) { net.Type = netDetails.Type net.Gateway = netDetails.Gateway net.Netmask = netDetails.Netmask + net.Offering = netDetails.Networkofferingname if netDetails.Vpcid != "" { if net.VPC == nil { net.VPC = &infrav1.VPC{} diff --git a/pkg/cloud/vpc.go b/pkg/cloud/vpc.go index 65ba370a..b9946c7a 100644 --- a/pkg/cloud/vpc.go +++ b/pkg/cloud/vpc.go @@ -40,8 +40,8 @@ type VPCIface interface { } // getVPCOfferingID fetches a vpc offering id. -func (c *client) getVPCOfferingID() (string, error) { - offeringID, count, retErr := c.cs.VPC.GetVPCOfferingID(VPCOffering) +func (c *client) getVPCOfferingID(offeringName string) (string, error) { + offeringID, count, retErr := c.cs.VPC.GetVPCOfferingID(offeringName) if retErr != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(retErr) return "", retErr @@ -70,6 +70,7 @@ func (c *client) ResolveVPC(vpc *infrav1.VPC) error { } vpc.Name = resp.Name vpc.CIDR = resp.Cidr + vpc.Offering = resp.Vpcofferingname return nil } @@ -84,6 +85,7 @@ func (c *client) ResolveVPC(vpc *infrav1.VPC) error { } vpc.ID = resp.Id vpc.CIDR = resp.Cidr + vpc.Offering = resp.Vpcofferingname return nil } @@ -93,7 +95,12 @@ func (c *client) CreateVPC(fd *infrav1.CloudStackFailureDomain, vpc *infrav1.VPC return errors.New("VPC name must be specified") } - offeringID, err := c.getVPCOfferingID() + offeringName := VPCOffering + if vpc.Offering != "" { + offeringName = vpc.Offering + } + + offeringID, err := c.getVPCOfferingID(offeringName) if err != nil { return err } diff --git a/test/e2e/common.go b/test/e2e/common.go index 0483359a..63f01179 100644 --- a/test/e2e/common.go +++ b/test/e2e/common.go @@ -62,12 +62,25 @@ const ( InvalidDomainName = "CLOUDSTACK_INVALID_DOMAIN_NAME" InvalidTemplateName = "CLOUDSTACK_INVALID_TEMPLATE_NAME" InvalidCPOfferingName = "CLOUDSTACK_INVALID_CONTROL_PLANE_MACHINE_OFFERING" + + CustomNetworkOfferingWithEgressPolicyName = "CLOUDSTACK_NETWORK_CUSTOM_OFFERING_EGRESS_POLICY_TRUE_NAME" + NetworkNameWithCustomOffering = "CLOUDSTACK_NETWORK_NAME_WITH_CUSTOM_OFFERING" + + VPCName = "CLOUDSTACK_VPC_NAME" + VPCNetworkName = "CLOUDSTACK_VPC_NETWORK_NAME" + CustomVPCOfferingName = "CLOUDSTACK_CUSTOM_VPC_OFFERING_NAME" + CustomVPCNetworkOfferingName = "CLOUDSTACK_CUSTOM_VPC_NETWORK_OFFERING_NAME" + VPCWithCustomOfferingName = "CLOUDSTACK_VPC_NAME_WITH_CUSTOM_OFFERING" + VPCNetworkWithCustomOfferingName = "CLOUDSTACK_VPC_NETWORK_NAME_WITH_CUSTOM_OFFERING" ) const ( ControlPlaneIndicator = "-control-plane-" MachineDeploymentIndicator = "-md-" DataVolumePrefix = "DATA-" + DefaultVPCOffering = "Default VPC offering" + DefaultVPCNetworkOffering = "DefaultIsolatedNetworkOfferingForVpcNetworks" + DefaultNetworkOffering = "DefaultIsolatedNetworkOfferingWithSourceNatService" ) type CommonSpecInput struct { @@ -391,15 +404,11 @@ func CheckNetworkExists(client *cloudstack.CloudStackClient, networkName string) } return false, err } else if count > 1 { - return false, fmt.Errorf("Expected 0-1 Network with name %s, but got %d.", networkName, count) + return false, fmt.Errorf("expected 0-1 network with name %s, but got %d", networkName, count) } return count == 1, nil } -func CheckVPCExists(client *cloudstack.CloudStackClient, vpcName string) (bool, error) { - return CheckVPCExistsInProject(client, vpcName, "") -} - func CheckVPCExistsInProject(client *cloudstack.CloudStackClient, vpcName string, project string) (bool, error) { p := client.VPC.NewListVPCsParams() p.SetName(vpcName) @@ -419,7 +428,7 @@ func CheckVPCExistsInProject(client *cloudstack.CloudStackClient, vpcName string } return false, err } else if listResp.Count > 1 { - return false, fmt.Errorf("Expected 0-1 VPC with name %s, but got %d.", vpcName, listResp.Count) + return false, fmt.Errorf("expected 0-1 vpc with name %s, but got %d", vpcName, listResp.Count) } return listResp.Count == 1, nil } diff --git a/test/e2e/config/cloudstack.yaml b/test/e2e/config/cloudstack.yaml index 7548fdd2..7febe8b4 100644 --- a/test/e2e/config/cloudstack.yaml +++ b/test/e2e/config/cloudstack.yaml @@ -89,6 +89,8 @@ providers: - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-second-cluster.yaml" - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-shared-network-kubevip.yaml" - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-vpc-network.yaml" + - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering.yaml" + - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering.yaml" - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-invalid-disk-offering.yaml" - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-invalid-disk-offering-size-for-non-customized.yaml" - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-invalid-disk-offering-size-for-customized.yaml" @@ -129,10 +131,20 @@ variables: CLOUDSTACK_INVALID_ACCOUNT_NAME: accountXXXX CLOUDSTACK_DOMAIN_NAME: ROOT CLOUDSTACK_INVALID_DOMAIN_NAME: domainXXXX + CLOUDSTACK_NETWORK_NAME: isolated-for-e2e-1 + CLOUDSTACK_NETWORK_NAME_WITH_CUSTOM_OFFERING: isolated-for-e2e-1-with-custom-offering + CLOUDSTACK_NETWORK_CUSTOM_OFFERING_EGRESS_POLICY_TRUE_NAME: CustomNetworkOfferingWithEgressPolicyTrue + CLOUDSTACK_VPC_NETWORK_NAME: vpc-isolated-for-e2e-1 CLOUDSTACK_VPC_NAME: vpc-for-e2e-1 CLOUDSTACK_VPC_CIDR: 10.10.0.0/16 + + CLOUDSTACK_VPC_NAME_WITH_CUSTOM_OFFERING: vpc-for-e2e-1-with-custom-offering + CLOUDSTACK_VPC_NETWORK_NAME_WITH_CUSTOM_OFFERING: vpc-isolated-for-e2e-1-with-custom-offering + CLOUDSTACK_CUSTOM_VPC_OFFERING_NAME: "CustomVPCOffering" + CLOUDSTACK_CUSTOM_VPC_NETWORK_OFFERING_NAME: "CustomVPCNetworkOffering" + CLOUDSTACK_GATEWAY: 10.10.0.1 CLOUDSTACK_NETMASK: 255.255.255.0 CLOUDSTACK_NEW_NETWORK_NAME: isolated-for-e2e-new diff --git a/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering/cluster-with-custom-vpc-offering.yaml b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering/cluster-with-custom-vpc-offering.yaml new file mode 100644 index 00000000..d007915c --- /dev/null +++ b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering/cluster-with-custom-vpc-offering.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta3 +kind: CloudStackCluster +metadata: + name: ${CLUSTER_NAME} +spec: + failureDomains: + - name: ${CLOUDSTACK_FD1_NAME} + acsEndpoint: + name: ${CLOUDSTACK_FD1_SECRET_NAME} + namespace: default + zone: + name: ${CLOUDSTACK_ZONE_NAME} + network: + name: ${CLOUDSTACK_VPC_NETWORK_NAME_WITH_CUSTOM_OFFERING} + gateway: ${CLOUDSTACK_GATEWAY} + netmask: ${CLOUDSTACK_NETMASK} + offering: ${CLOUDSTACK_CUSTOM_VPC_NETWORK_OFFERING_NAME} + vpc: + name: ${CLOUDSTACK_VPC_NAME_WITH_CUSTOM_OFFERING} + cidr: ${CLOUDSTACK_VPC_CIDR} + offering: ${CLOUDSTACK_CUSTOM_VPC_OFFERING_NAME} + controlPlaneEndpoint: + host: "" + port: 6443 diff --git a/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering/kustomization.yaml b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering/kustomization.yaml new file mode 100644 index 00000000..0da8be4e --- /dev/null +++ b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-custom-vpc-offering/kustomization.yaml @@ -0,0 +1,6 @@ +bases: + - ../bases/cluster-with-kcp.yaml + - ../bases/md.yaml + +patchesStrategicMerge: +- ./cluster-with-custom-vpc-offering.yaml diff --git a/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering/cluster-with-network-with-custom-offering.yaml b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering/cluster-with-network-with-custom-offering.yaml new file mode 100644 index 00000000..91110b94 --- /dev/null +++ b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering/cluster-with-network-with-custom-offering.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta3 +kind: CloudStackCluster +metadata: + name: ${CLUSTER_NAME} +spec: + failureDomains: + - name: ${CLOUDSTACK_FD1_NAME} + acsEndpoint: + name: ${CLOUDSTACK_FD1_SECRET_NAME} + namespace: default + zone: + name: ${CLOUDSTACK_ZONE_NAME} + network: + name: ${CLOUDSTACK_NETWORK_NAME_WITH_CUSTOM_OFFERING} + gateway: ${CLOUDSTACK_GATEWAY} + netmask: ${CLOUDSTACK_NETMASK} + offering: ${CLOUDSTACK_NETWORK_CUSTOM_OFFERING_EGRESS_POLICY_TRUE_NAME} + controlPlaneEndpoint: + host: "" + port: 6443 diff --git a/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering/kustomization.yaml b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering/kustomization.yaml new file mode 100644 index 00000000..c84bde4b --- /dev/null +++ b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-network-with-custom-offering/kustomization.yaml @@ -0,0 +1,6 @@ +bases: + - ../bases/cluster-with-kcp.yaml + - ../bases/md.yaml + +patchesStrategicMerge: +- ./cluster-with-network-with-custom-offering.yaml \ No newline at end of file diff --git a/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-vpc-network/cluster-with-vpc-network.yaml b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-vpc-network/cluster-with-vpc-network.yaml index fae538b1..b2e72734 100644 --- a/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-vpc-network/cluster-with-vpc-network.yaml +++ b/test/e2e/data/infrastructure-cloudstack/v1beta3/cluster-template-vpc-network/cluster-with-vpc-network.yaml @@ -12,7 +12,7 @@ spec: zone: name: ${CLOUDSTACK_ZONE_NAME} network: - name: ${CLOUDSTACK_NETWORK_NAME} + name: ${CLOUDSTACK_VPC_NETWORK_NAME} gateway: ${CLOUDSTACK_GATEWAY} netmask: ${CLOUDSTACK_NETMASK} vpc: diff --git a/test/e2e/network_with_custom_offering.go b/test/e2e/network_with_custom_offering.go new file mode 100644 index 00000000..41079458 --- /dev/null +++ b/test/e2e/network_with_custom_offering.go @@ -0,0 +1,107 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "context" + "fmt" + "os" + "path/filepath" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" + + "sigs.k8s.io/cluster-api/test/framework/clusterctl" + "sigs.k8s.io/cluster-api/util" +) + +// NetworkWithCustomOfferingSpec implements a test that verifies that an app deployed to the workload cluster works. +func NetworkWithCustomOfferingSpec(ctx context.Context, inputGetter func() CommonSpecInput) { + var ( + specName = "network-with-custom-offering" + input CommonSpecInput + namespace *corev1.Namespace + cancelWatches context.CancelFunc + clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult + networkOfferingName string + networkName string + ) + + BeforeEach(func() { + Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName) + input = inputGetter() + Expect(input.E2EConfig).ToNot(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName) + Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName) + Expect(input.BootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName) + Expect(os.MkdirAll(input.ArtifactFolder, 0750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName) + + Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion)) + + // Setup a Namespace where to host objects for this spec and create a watcher for the namespace events. + namespace, cancelWatches = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder) + clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult) + + networkOfferingName = input.E2EConfig.GetVariable(CustomNetworkOfferingWithEgressPolicyName) + networkName = input.E2EConfig.GetVariable(NetworkNameWithCustomOffering) + + csClient := CreateCloudStackClient(ctx, input.BootstrapClusterProxy.GetKubeconfigPath()) + exists, err := CheckNetworkExists(csClient, networkName) + Expect(err).To(BeNil()) + Expect(exists).To(BeFalse()) + }) + + It("Should create a new network with a custom offering", func() { + clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{ + ClusterProxy: input.BootstrapClusterProxy, + CNIManifestPath: input.E2EConfig.GetVariable(CNIPath), + ConfigCluster: clusterctl.ConfigClusterInput{ + LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()), + ClusterctlConfigPath: input.ClusterctlConfigPath, + KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), + InfrastructureProvider: clusterctl.DefaultInfrastructureProvider, + Flavor: specName, + Namespace: namespace.Name, + ClusterName: fmt.Sprintf("%s-%s", specName, util.RandomString(6)), + KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion), + ControlPlaneMachineCount: pointer.Int64(1), + WorkerMachineCount: pointer.Int64(1), + }, + WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"), + WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"), + WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"), + }, clusterResources) + + csClient := CreateCloudStackClient(ctx, input.BootstrapClusterProxy.GetKubeconfigPath()) + + Byf("Checking if network %s exists with offering %s with egress policy %t", networkName, networkOfferingName, true) + network, count, err := csClient.Network.GetNetworkByName(networkName) + Expect(err).To(BeNil()) + Expect(count).To(BeEquivalentTo(1)) + Expect(network.Name).To(BeEquivalentTo(networkName)) + Expect(network.Networkofferingname).To(BeEquivalentTo(networkOfferingName)) + Expect(network.Egressdefaultpolicy).To(BeEquivalentTo(true)) + + By("PASSED!") + }) + + AfterEach(func() { + // Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself. + dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup) + }) +} diff --git a/test/e2e/network_with_custom_offering_test.go b/test/e2e/network_with_custom_offering_test.go new file mode 100644 index 00000000..69b70e73 --- /dev/null +++ b/test/e2e/network_with_custom_offering_test.go @@ -0,0 +1,39 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("When testing network with custom offering", func() { + NetworkWithCustomOfferingSpec(context.TODO(), func() CommonSpecInput { + return CommonSpecInput{ + E2EConfig: e2eConfig, + ClusterctlConfigPath: clusterctlConfigPath, + BootstrapClusterProxy: bootstrapClusterProxy, + ArtifactFolder: artifactFolder, + SkipCleanup: skipCleanup, + } + }) + +}) diff --git a/test/e2e/resource_cleanup.go b/test/e2e/resource_cleanup.go index c2bd13e7..79eee150 100644 --- a/test/e2e/resource_cleanup.go +++ b/test/e2e/resource_cleanup.go @@ -88,6 +88,14 @@ func ResourceCleanupSpec(ctx context.Context, inputGetter func() CommonSpecInput Expect(err).To(BeNil()) Expect(exists).To(BeTrue()) + Byf("Checking if network %s exists with offering %s with egress policy %t", networkName, DefaultNetworkOffering, false) + network, count, err := csClient.Network.GetNetworkByName(networkName) + Expect(err).To(BeNil()) + Expect(count).To(BeEquivalentTo(1)) + Expect(network.Name).To(BeEquivalentTo(networkName)) + Expect(network.Networkofferingname).To(BeEquivalentTo(DefaultNetworkOffering)) + Expect(network.Egressdefaultpolicy).To(BeEquivalentTo(false)) + By("PASSED!") }) diff --git a/test/e2e/vpc_network.go b/test/e2e/vpc_network.go index 44fa1363..095389dd 100644 --- a/test/e2e/vpc_network.go +++ b/test/e2e/vpc_network.go @@ -39,7 +39,6 @@ func VPCNetworkSpec(ctx context.Context, inputGetter func() CommonSpecInput) { namespace *corev1.Namespace cancelWatches context.CancelFunc clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult - vpcName = "vpc-for-e2e-1" ) BeforeEach(func() { @@ -57,35 +56,21 @@ func VPCNetworkSpec(ctx context.Context, inputGetter func() CommonSpecInput) { clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult) }) - It("Should successfully create a cluster in a VPC network", func() { - By("Creating a workload cluster in a VPC network") - - clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{ - ClusterProxy: input.BootstrapClusterProxy, - CNIManifestPath: input.E2EConfig.GetVariable(CNIPath), - ConfigCluster: clusterctl.ConfigClusterInput{ - LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()), - ClusterctlConfigPath: input.ClusterctlConfigPath, - KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), - InfrastructureProvider: clusterctl.DefaultInfrastructureProvider, - Flavor: specName, - Namespace: namespace.Name, - ClusterName: fmt.Sprintf("%s-%s", specName, util.RandomString(6)), - KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion), - ControlPlaneMachineCount: pointer.Int64(1), - WorkerMachineCount: pointer.Int64(1), - }, - WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"), - WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"), - WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"), - }, clusterResources) - - csClient := CreateCloudStackClient(ctx, input.BootstrapClusterProxy.GetKubeconfigPath()) - - exists, err := CheckVPCExists(csClient, vpcName) - Expect(err).To(BeNil()) - Expect(exists).To(BeTrue()) + It("Should successfully create a cluster in a VPC network with default offering", func() { + By("Creating a workload cluster in a VPC network with default offering") + vpcName := input.E2EConfig.GetVariable(VPCName) + networkName := input.E2EConfig.GetVariable(VPCNetworkName) + setupClusterWithVpcAndVerifyOfferingName(ctx, input, namespace, clusterResources, specName, "vpc-network", vpcName, DefaultVPCOffering, networkName, DefaultVPCNetworkOffering) + By("PASSED!") + }) + It("Should successfully create a cluster in a VPC network with a custom offering", func() { + By("Creating a workload cluster in a VPC network with a custom offering") + vpcWithCustomOfferingName := input.E2EConfig.GetVariable(VPCWithCustomOfferingName) + networkName := input.E2EConfig.GetVariable(VPCNetworkWithCustomOfferingName) + customVpcOfferingName := input.E2EConfig.GetVariable(CustomVPCOfferingName) + customVpcNetworkOfferingName := input.E2EConfig.GetVariable(CustomVPCNetworkOfferingName) + setupClusterWithVpcAndVerifyOfferingName(ctx, input, namespace, clusterResources, specName, "custom-vpc-offering", vpcWithCustomOfferingName, customVpcOfferingName, networkName, customVpcNetworkOfferingName) By("PASSED!") }) @@ -94,3 +79,42 @@ func VPCNetworkSpec(ctx context.Context, inputGetter func() CommonSpecInput) { dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup) }) } + +func setupClusterWithVpcAndVerifyOfferingName(ctx context.Context, input CommonSpecInput, namespace *corev1.Namespace, + clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult, specName string, flavor string, vpcName string, + vpcOfferingName string, networkName string, networkOfferingName string, +) { + clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{ + ClusterProxy: input.BootstrapClusterProxy, + CNIManifestPath: input.E2EConfig.GetVariable(CNIPath), + ConfigCluster: clusterctl.ConfigClusterInput{ + LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()), + ClusterctlConfigPath: input.ClusterctlConfigPath, + KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), + InfrastructureProvider: clusterctl.DefaultInfrastructureProvider, + Flavor: flavor, + Namespace: namespace.Name, + ClusterName: fmt.Sprintf("%s-%s", specName, util.RandomString(6)), + KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion), + ControlPlaneMachineCount: pointer.Int64(1), + WorkerMachineCount: pointer.Int64(1), + }, + WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"), + WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"), + WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"), + }, clusterResources) + + csClient := CreateCloudStackClient(ctx, input.BootstrapClusterProxy.GetKubeconfigPath()) + + Byf("Checking if VPC %s exists with offering %s", vpcName, vpcOfferingName) + vpc, count, err := csClient.VPC.GetVPCByName(vpcName) + Expect(err).To(BeNil()) + Expect(count).To(BeEquivalentTo(1)) + Expect(vpc.Vpcofferingname).To(BeEquivalentTo(vpcOfferingName)) + + Byf("Checking if network %s exists with offering %s", networkName, networkOfferingName) + network, count, err := csClient.Network.GetNetworkByName(networkName) + Expect(err).To(BeNil()) + Expect(count).To(BeEquivalentTo(1)) + Expect(network.Networkofferingname).To(BeEquivalentTo(networkOfferingName)) +}