diff --git a/Makefile b/Makefile index 15fa5d5e8..508c63b20 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=build/yaml/crd/ + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1;github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" output:crd:artifacts:config=build/yaml/crd/ .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -56,7 +56,7 @@ vet: ## Run go vet against code. .PHONY: test test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -gcflags=all=-l ./... -coverprofile cover.out ## Prohibit inline optimization when using gomonkey ##@ Build @@ -64,6 +64,10 @@ test: manifests generate fmt vet envtest ## Run tests. build: generate fmt vet ## Build manager binary. go build -o bin/manager cmd/main.go +.PHONY: clean +clean: generate fmt vet ## Build clean binary. + go build -o bin/clean cmd_clean/main.go + .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. go run ./cmd/main.go @@ -113,6 +117,9 @@ KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) +generated: + ./hack/update-codegen.sh + ENVTEST = $(shell pwd)/bin/setup-envtest .PHONY: envtest envtest: ## Download envtest-setup locally if necessary. diff --git a/PROJECT b/PROJECT index a3ee540b0..5bd38683a 100644 --- a/PROJECT +++ b/PROJECT @@ -35,4 +35,11 @@ resources: kind: NSXServiceAccount path: github.com/vmware-tanzu/nsx-operator/pkg/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: nsx.vmware.com + kind: IPPool + path: github.com/vmware-tanzu/nsx-operator/pkg/api/v1alpha2 + version: v1alpha2 version: "3" diff --git a/build/yaml/crd/nsx.vmware.com_ippools.yaml b/build/yaml/crd/nsx.vmware.com_ippools.yaml index 36f4542e1..e6cf505c4 100644 --- a/build/yaml/crd/nsx.vmware.com_ippools.yaml +++ b/build/yaml/crd/nsx.vmware.com_ippools.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.11.0 creationTimestamp: null name: ippools.nsx.vmware.com spec: @@ -18,31 +18,120 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: + description: IPPool is the Schema for the ippools API. properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object spec: + description: IPPoolSpec defines the desired state of IPPool. properties: subnets: + description: Subnets defines set of subnets need to be allocated. items: + description: SubnetRequest defines the subnet allocation request. properties: ipFamily: - pattern: ^ipv(4|6)$ + default: IPv4 + description: IPFamily defines the IP family type for this subnet, + could be IPv4 or IPv6. This is optional, the default is IPv4. + enum: + - IPv4 + - IPv6 type: string name: + description: Name defines the name of this subnet. type: string prefixLength: - minimum: 1 + description: PrefixLength defines prefix length for this subnet. type: integer + required: + - name type: object type: array type: object + status: + description: IPPoolStatus defines the observed state of IPPool. + properties: + conditions: + description: Conditions defines current state of the IPPool. + items: + description: Condition defines condition of custom resource. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: Message shows a human-readable message about condition. + type: string + reason: + description: Reason shows a brief reason of condition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type defines condition type. + type: string + required: + - status + - type + type: object + type: array + subnets: + description: Subnets defines subnets allocation result. + items: + description: SubnetResult defines the subnet allocation result. + properties: + cidr: + description: CIDR defines the allocated CIDR. + type: string + name: + description: Name defines the name of this subnet. + type: string + required: + - cidr + - name + type: object + type: array + required: + - conditions + - subnets + type: object + required: + - metadata + - spec type: object - x-kubernetes-preserve-unknown-fields: true served: true storage: false - - name: v1alpha2 + subresources: + status: {} + - additionalPrinterColumns: + - description: Type of IPPool + jsonPath: .spec.type + name: Type + type: string + - description: CIDRs for the Subnet + jsonPath: .status.subnets[*].cidr + name: Subnets + type: string + name: v1alpha2 schema: openAPIV3Schema: - description: IPPool is the Schema for the ippools API + description: IPPool is the Schema for the ippools API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -83,14 +172,11 @@ spec: type: object type: array type: - default: private - description: Type defines the type of this IPPool, public or private. + description: Type defines the type of this IPPool, Public or Private. enum: - - public - - private + - Public + - Private type: string - required: - - type type: object status: description: IPPoolStatus defines the observed state of IPPool. @@ -152,9 +238,3 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: ["v1alpha2"] \ No newline at end of file diff --git a/build/yaml/crd/nsx.vmware.com_staticroutes.yaml b/build/yaml/crd/nsx.vmware.com_staticroutes.yaml index cd0f94c39..b53ef56e7 100644 --- a/build/yaml/crd/nsx.vmware.com_staticroutes.yaml +++ b/build/yaml/crd/nsx.vmware.com_staticroutes.yaml @@ -15,7 +15,16 @@ spec: singular: staticroute scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: Network in CIDR format + jsonPath: .spec.network + name: Network + type: string + - description: Next Hops + jsonPath: .spec.nextHops[*].ipAddress + name: NextHops + type: string + name: v1alpha1 schema: openAPIV3Schema: description: StaticRoute is the Schema for the staticroutes API. @@ -88,8 +97,11 @@ spec: - type type: object type: array + nsxResourcePath: + type: string required: - conditions + - nsxResourcePath type: object type: object served: true diff --git a/build/yaml/crd/nsx.vmware.com_subnetports.yaml b/build/yaml/crd/nsx.vmware.com_subnetports.yaml index 6fe8a7f0c..9835d95af 100644 --- a/build/yaml/crd/nsx.vmware.com_subnetports.yaml +++ b/build/yaml/crd/nsx.vmware.com_subnetports.yaml @@ -15,7 +15,20 @@ spec: singular: subnetport scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: Attachment VIF ID owned by the SubnetPort + jsonPath: .status.vifID + name: VIFID + type: string + - description: IP Address of the SubnetPort + jsonPath: .status.ipAddresses[0].ip + name: IPAddress + type: string + - description: MAC Address of the SubnetPort + jsonPath: .status.macAddress + name: MACAddress + type: string + name: v1alpha1 schema: openAPIV3Schema: description: SubnetPort is the Schema for the subnetports API. diff --git a/build/yaml/crd/nsx.vmware.com_subnets.yaml b/build/yaml/crd/nsx.vmware.com_subnets.yaml index ba98d90f9..7d5de517b 100644 --- a/build/yaml/crd/nsx.vmware.com_subnets.yaml +++ b/build/yaml/crd/nsx.vmware.com_subnets.yaml @@ -15,7 +15,20 @@ spec: singular: subnet scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: Access mode of Subnet + jsonPath: .spec.accessMode + name: AccessMode + type: string + - description: Size of Subnet + jsonPath: .spec.ipv4SubnetSize + name: IPv4SubnetSize + type: string + - description: CIDRs for the Subnet + jsonPath: .status.ipAddresses[*] + name: IPAddresses + type: string + name: v1alpha1 schema: openAPIV3Schema: description: Subnet is the Schema for the subnets API. @@ -67,12 +80,11 @@ spec: type: boolean type: object accessMode: - default: private description: Access mode of Subnet, accessible only from within VPC - or from outside VPC. Defaults to private. + or from outside VPC. enum: - - private - - public + - Private + - Public type: string advancedConfig: description: Subnet advanced configuration. @@ -96,9 +108,7 @@ spec: minItems: 0 type: array ipv4SubnetSize: - default: 64 - description: Size of Subnet based upon estimated workload count. Defaults - to 64. + description: Size of Subnet based upon estimated workload count. maximum: 65536 minimum: 16 type: integer @@ -140,10 +150,6 @@ spec: type: array nsxResourcePath: type: string - required: - - conditions - - ipAddresses - - nsxResourcePath type: object type: object served: true diff --git a/build/yaml/crd/nsx.vmware.com_subnetsets.yaml b/build/yaml/crd/nsx.vmware.com_subnetsets.yaml index dcec5eb20..aac120f98 100644 --- a/build/yaml/crd/nsx.vmware.com_subnetsets.yaml +++ b/build/yaml/crd/nsx.vmware.com_subnetsets.yaml @@ -15,7 +15,20 @@ spec: singular: subnetset scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: Access mode of Subnet + jsonPath: .spec.accessMode + name: AccessMode + type: string + - description: Size of Subnet + jsonPath: .spec.ipv4SubnetSize + name: IPv4SubnetSize + type: string + - description: CIDRs for the Subnet + jsonPath: .status.subnets[*].ipAddresses[*] + name: IPAddresses + type: string + name: v1alpha1 schema: openAPIV3Schema: description: SubnetSet is the Schema for the subnetsets API. @@ -67,12 +80,11 @@ spec: type: boolean type: object accessMode: - default: private description: Access mode of Subnet, accessible only from within VPC - or from outside VPC. Defaults to private. + or from outside VPC. enum: - - private - - public + - Private + - Public type: string advancedConfig: description: Subnet advanced configuration. @@ -89,9 +101,7 @@ spec: type: object type: object ipv4SubnetSize: - default: 64 - description: Size of Subnet based upon estimated workload count. Defaults - to 64. + description: Size of Subnet based upon estimated workload count. maximum: 65536 minimum: 16 type: integer @@ -143,9 +153,6 @@ spec: - nsxResourcePath type: object type: array - required: - - conditions - - subnets type: object type: object served: true diff --git a/build/yaml/crd/nsx.vmware.com_vpcnetworkconfigurations.yaml b/build/yaml/crd/nsx.vmware.com_vpcnetworkconfigurations.yaml index 1865d4582..f77272d89 100644 --- a/build/yaml/crd/nsx.vmware.com_vpcnetworkconfigurations.yaml +++ b/build/yaml/crd/nsx.vmware.com_vpcnetworkconfigurations.yaml @@ -17,15 +17,15 @@ spec: versions: - additionalPrinterColumns: - description: NSXTProject the Namespace associated with - jsonPath: .spec.NSXTProject + jsonPath: .spec.nsxtProject name: NSXTProject type: string - description: ExternalIPv4Blocks assigned to the Namespace - jsonPath: .spec.ExternalIPv4Blocks - name: PublicIPv4Blocks + jsonPath: .spec.externalIPv4Blocks + name: ExternalIPv4Blocks type: string - description: PrivateIPv4CIDRs assigned to the Namespace - jsonPath: .spec.PrivateIPv4CIDRs + jsonPath: .spec.privateIPv4CIDRs name: PrivateIPv4CIDRs type: string name: v1alpha1 @@ -63,10 +63,10 @@ spec: type: integer defaultSubnetAccessMode: description: DefaultSubnetAccessMode defines the access mode of the - default SubnetSet for PodVM and VM. Must be public or private. + default SubnetSet for PodVM and VM. Must be Public or Private. enum: - - public - - private + - Public + - Private type: string edgeClusterPath: description: Edge cluster path on which the networking elements will @@ -79,19 +79,11 @@ spec: maxItems: 5 minItems: 0 type: array - loadBalancerVPCEndpoint: - description: Load balancer endpoint configuration. - properties: - enabled: - default: false - description: Flag to enable load balancer for vpc. - type: boolean - type: object nsxtProject: description: NSX-T Project the Namespace associated with. type: string privateIPv4CIDRs: - description: Private IPv4 CIDRs used to allocate private Subnets. + description: Private IPv4 CIDRs used to allocate Private Subnets. items: type: string maxItems: 5 diff --git a/build/yaml/crd/nsx.vmware.com_vpcs.yaml b/build/yaml/crd/nsx.vmware.com_vpcs.yaml index faf9b1847..4770ab615 100644 --- a/build/yaml/crd/nsx.vmware.com_vpcs.yaml +++ b/build/yaml/crd/nsx.vmware.com_vpcs.yaml @@ -1,4 +1,3 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -16,7 +15,20 @@ spec: singular: vpc scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: Private IPv4 CIDRs + jsonPath: .status.privateIPv4CIDRs + name: PrivateIPv4CIDRs + type: string + - description: Default SNAT IP for Private Subnets + jsonPath: .status.defaultSNATIP + name: SNATIP + type: string + - description: CIDR for the load balancer Subnet + jsonPath: .status.lbSubnetCIDR + name: LBSubnetCIDR + type: string + name: v1alpha1 schema: openAPIV3Schema: description: VPC is the Schema for the VPC API @@ -68,7 +80,7 @@ spec: type: object type: array defaultSNATIP: - description: Default SNAT IP for private Subnets. + description: Default SNAT IP for Private Subnets. type: string lbSubnetCIDR: description: CIDR for the load balancer Subnet. @@ -79,21 +91,21 @@ spec: nsxResourcePath: description: NSX VPC Policy API resource path. type: string + privateIPv4CIDRs: + description: Private CIDRs used for the VPC. + items: + type: string + type: array required: - conditions - defaultSNATIP - lbSubnetCIDR - lbSubnetPath - nsxResourcePath + - privateIPv4CIDRs type: object type: object served: true storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/build/yaml/samples/nsx_v1alpha1_staticroute.yaml b/build/yaml/samples/nsx_v1alpha1_staticroute.yaml new file mode 100644 index 000000000..704972ffe --- /dev/null +++ b/build/yaml/samples/nsx_v1alpha1_staticroute.yaml @@ -0,0 +1,10 @@ +apiVersion: nsx.vmware.com/v1alpha1 +kind: StaticRoute +metadata: + name: test-2 + namespace: qe +spec: + network: 45.1.2.0/24 + nextHops: + - ipAddress: 172.10.0.2 + - ipAddress: 172.10.0.1 diff --git a/build/yaml/samples/nsx_v1alpha1_vpcnetworkconfigurations.yaml b/build/yaml/samples/nsx_v1alpha1_vpcnetworkconfigurations.yaml index 4172ff36b..c19703c2f 100644 --- a/build/yaml/samples/nsx_v1alpha1_vpcnetworkconfigurations.yaml +++ b/build/yaml/samples/nsx_v1alpha1_vpcnetworkconfigurations.yaml @@ -8,8 +8,8 @@ spec: defaultIPv4SubnetSize: 26 nsxtProject: proj-1 externalIPv4Blocks: - - /infra/ip-blocks/block1 + - block1 privateIPv4CIDRs: - 172.26.0.0/16 - 172.36.0.0/16 - defaultSubnetAccessMode: public + defaultSubnetAccessMode: Private diff --git a/build/yaml/samples/nsx_v1alpha1_vpcs.yaml b/build/yaml/samples/nsx_v1alpha1_vpcs.yaml new file mode 100644 index 000000000..99b9afc14 --- /dev/null +++ b/build/yaml/samples/nsx_v1alpha1_vpcs.yaml @@ -0,0 +1,6 @@ +apiVersion: nsx.vmware.com/v1alpha1 +kind: VPC +metadata: + name: vpc-default +spec: + diff --git a/build/yaml/samples/nsx_v1alpha2_ippool.yaml b/build/yaml/samples/nsx_v1alpha2_ippool.yaml index 40ca4ba7f..41713643d 100644 --- a/build/yaml/samples/nsx_v1alpha2_ippool.yaml +++ b/build/yaml/samples/nsx_v1alpha2_ippool.yaml @@ -4,7 +4,6 @@ metadata: name: guestcluster-ippool namespace: sc-a spec: - type: public subnets: - ipFamily: IPv4 name: guestcluster1-workers-a diff --git a/cmd/main.go b/cmd/main.go index 159c1878d..7a5399c58 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,6 +7,7 @@ import ( "os" "time" + vmv1alpha1 "github.com/vmware-tanzu/vm-operator/api/v1alpha1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -15,16 +16,28 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" "github.com/vmware-tanzu/nsx-operator/pkg/config" commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + ippool2 "github.com/vmware-tanzu/nsx-operator/pkg/controllers/ippool" + namespacecontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/namespace" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/node" nsxserviceaccountcontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/nsxserviceaccount" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/pod" securitypolicycontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/securitypolicy" + staticroutecontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/staticroute" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/subnet" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/subnetport" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/subnetset" + vpccontroller "github.com/vmware-tanzu/nsx-operator/pkg/controllers/vpc" "github.com/vmware-tanzu/nsx-operator/pkg/logger" "github.com/vmware-tanzu/nsx-operator/pkg/metrics" "github.com/vmware-tanzu/nsx-operator/pkg/nsx" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/ippool" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/nsxserviceaccount" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/securitypolicy" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" ) var ( @@ -38,6 +51,8 @@ func init() { var err error utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(v1alpha2.AddToScheme(scheme)) + utilruntime.Must(vmv1alpha1.AddToScheme(scheme)) config.AddFlags() cf, err = config.NewNSXOperatorConfigFromFile() @@ -72,7 +87,6 @@ func StartSecurityPolicyController(mgr ctrl.Manager, commonService common.Servic os.Exit(1) } else { securityReconcile.Service = securityService - commonctl.ServiceMediator.SecurityPolicyService = securityService } if err := securityReconcile.Start(mgr); err != nil { log.Error(err, "failed to create controller", "controller", "SecurityPolicy") @@ -98,6 +112,54 @@ func StartNSXServiceAccountController(mgr ctrl.Manager, commonService common.Ser } } +func StartIPPoolController(mgr ctrl.Manager, commonService common.Service) { + ippoolReconcile := &ippool2.IPPoolReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if ipPoolService, err := ippool.InitializeIPPool(commonService); err != nil { + log.Error(err, "failed to initialize ippool commonService", "controller", "IPPool") + os.Exit(1) + } else { + ippoolReconcile.Service = ipPoolService + } + if err := ippoolReconcile.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "IPPool") + os.Exit(1) + } +} + +func StartVPCController(mgr ctrl.Manager, commonService common.Service) { + vpcReconciler := &vpccontroller.VPCReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if vpcService, err := vpc.InitializeVPC(commonService); err != nil { + log.Error(err, "failed to initialize vpc commonService", "controller", "VPC") + os.Exit(1) + } else { + vpcReconciler.Service = vpcService + commonctl.ServiceMediator.VPCService = vpcService + } + if err := vpcReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create vpc controller", "controller", "VPC") + os.Exit(1) + } +} + +func StartNamespaceController(mgr ctrl.Manager, commonService common.Service) { + nsReconciler := &namespacecontroller.NamespaceReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + NSXConfig: commonService.NSXConfig, + } + + if err := nsReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create namespace controller", "controller", "Namespace") + os.Exit(1) + } +} + func main() { log.Info("starting NSX Operator") @@ -128,8 +190,30 @@ func main() { NSXConfig: cf, } + if cf.CoeConfig.EnableVPCNetwork && commonService.NSXClient.NSXCheckVersion(nsx.VPC) { + log.V(1).Info("VPC mode enabled") + // Start controllers which only supports VPC + // Start subnet/subnetset controller. + if err := subnet.StartSubnetController(mgr, commonService); err != nil { + os.Exit(1) + } + if err := subnetset.StartSubnetSetController(mgr, commonService); err != nil { + os.Exit(1) + } + + node.StartNodeController(mgr, commonService) + staticroutecontroller.StartStaticRouteController(mgr, commonService) + subnetport.StartSubnetPortController(mgr, commonService) + pod.StartPodController(mgr, commonService) + + StartNamespaceController(mgr, commonService) + StartVPCController(mgr, commonService) + StartIPPoolController(mgr, commonService) + } + // Start the security policy controller. StartSecurityPolicyController(mgr, commonService) + // Start the NSXServiceAccount controller. if cf.EnableAntreaNSXInterworking { StartNSXServiceAccountController(mgr, commonService) diff --git a/cmd_clean/main.go b/cmd_clean/main.go new file mode 100644 index 000000000..43c4ab7e5 --- /dev/null +++ b/cmd_clean/main.go @@ -0,0 +1,71 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package main + +import ( + "flag" + "os" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/vmware-tanzu/nsx-operator/pkg/clean" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" +) + +// usage: +// ./bin/clean -cluster='' -thumbprint="" -log-level=0 -vc-user="" -vc-passwd="" -vc-endpoint="" -vc-sso-domain="" -vc-https-port=443 -mgr-ip="" + +var ( + log = logger.Log + cf *config.NSXOperatorConfig + mgrIp string + vcEndpoint string + vcUser string + vcPasswd string + nsxUser string + nsxPasswd string + vcSsoDomain string + vcHttpsPort int + thumbprint string + caFile string + cluster string +) + +func main() { + flag.StringVar(&vcEndpoint, "vc-endpoint", "", "nsx manager ip") + flag.StringVar(&vcSsoDomain, "vc-sso-domain", "", "nsx manager ip") + flag.StringVar(&mgrIp, "mgr-ip", "", "nsx manager ip") + flag.StringVar(&vcUser, "vc-user", "", "vc username") + flag.StringVar(&vcPasswd, "vc-passwd", "", "vc password") + flag.IntVar(&vcHttpsPort, "vc-https-port", 443, "vc https port") + flag.StringVar(&thumbprint, "thumbprint", "", "nsx thumbprint") + flag.StringVar(&nsxUser, "nsx-user", "", "nsx username") + flag.StringVar(&nsxPasswd, "nsx-passwd", "", "nsx password") + flag.StringVar(&caFile, "ca-file", "", "ca file") + flag.StringVar(&cluster, "cluster", "", "cluster name") + flag.IntVar(&config.LogLevel, "log-level", 0, "Use zap-core log system.") + flag.Parse() + + cf = config.NewNSXOpertorConfig() + cf.NsxApiManagers = []string{mgrIp} + cf.VCUser = vcUser + cf.VCPassword = vcPasswd + cf.VCEndPoint = vcEndpoint + cf.NsxApiUser = nsxUser + cf.NsxApiPassword = nsxPasswd + cf.SsoDomain = vcSsoDomain + cf.HttpsPort = vcHttpsPort + cf.Thumbprint = []string{thumbprint} + cf.CaFile = []string{caFile} + cf.Cluster = cluster + logf.SetLogger(logger.ZapLogger(cf)) + + err := clean.Clean(cf) + if err != nil { + log.Error(err, "failed to clean nsx resources") + os.Exit(1) + } + os.Exit(0) +} diff --git a/go.mod b/go.mod index d255d1222..8f1928333 100644 --- a/go.mod +++ b/go.mod @@ -3,39 +3,46 @@ module github.com/vmware-tanzu/nsx-operator go 1.19 replace ( + github.com/vmware-tanzu/nsx-operator/pkg/apis => ./pkg/apis github.com/vmware/go-vmware-nsxt => github.com/mengdie-song/go-vmware-nsxt v0.0.0-20220921033217-dbd234470e30 // inventory-keeper includes all commits from github.com/gran-vmv/go-vmware-nsxt v0.0.0-20211206034609-cd7ffaf2c996 - github.com/vmware/vsphere-automation-sdk-go/lib => github.com/TaoZou1/vsphere-automation-sdk-go/lib v0.5.2 - github.com/vmware/vsphere-automation-sdk-go/runtime => github.com/TaoZou1/vsphere-automation-sdk-go/runtime v0.5.2 - github.com/vmware/vsphere-automation-sdk-go/services/nsxt => github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt v0.9.3 + github.com/vmware/vsphere-automation-sdk-go/lib => github.com/TaoZou1/vsphere-automation-sdk-go/lib v0.5.4 + github.com/vmware/vsphere-automation-sdk-go/runtime => github.com/TaoZou1/vsphere-automation-sdk-go/runtime v0.5.4 + github.com/vmware/vsphere-automation-sdk-go/services/nsxt => github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt v0.9.8 // update it from 0.9.5 to 0.9.8 to workaround the relization error for subnetport github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp => github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt-mp v0.3.1-0.20221020082725-84e89979deb6 ) require ( github.com/agiledragon/gomonkey v2.0.2+incompatible github.com/agiledragon/gomonkey/v2 v2.9.0 + github.com/apparentlymart/go-cidr v1.1.0 github.com/coreos/go-semver v0.3.0 github.com/deckarep/golang-set v1.8.0 - github.com/go-logr/logr v1.2.3 + github.com/go-logr/logr v1.2.4 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/mock v1.6.0 + github.com/google/uuid v1.2.0 github.com/kevinburke/ssh_config v1.2.0 github.com/openlyinc/pointy v1.1.2 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.8.0 + github.com/vmware-tanzu/nsx-operator/pkg/apis v0.0.0-00010101000000-000000000000 + github.com/vmware-tanzu/vm-operator/api v1.8.2 github.com/vmware/govmomi v0.27.4 - github.com/vmware/vsphere-automation-sdk-go/lib v0.5.0 - github.com/vmware/vsphere-automation-sdk-go/runtime v0.5.0 - github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.6.0 + github.com/vmware/vsphere-automation-sdk-go/lib v0.5.2 + github.com/vmware/vsphere-automation-sdk-go/runtime v0.5.2 + github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.11.0 github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.3.0 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.14.0 golang.org/x/time v0.3.0 gopkg.in/ini.v1 v1.66.4 - k8s.io/api v0.26.0 - k8s.io/apimachinery v0.26.0 - k8s.io/client-go v0.26.0 - sigs.k8s.io/controller-runtime v0.14.0 + k8s.io/api v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 + k8s.io/code-generator v0.26.1 + sigs.k8s.io/controller-runtime v0.14.5 ) require ( @@ -57,8 +64,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.2.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -68,7 +74,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect @@ -77,23 +82,26 @@ require ( github.com/stretchr/objx v0.4.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.26.0 // indirect - k8s.io/component-base v0.26.0 // indirect - k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/apiextensions-apiserver v0.26.1 // indirect + k8s.io/component-base v0.26.1 // indirect + k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect + k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 651cdce67..2d8defb3f 100644 --- a/go.sum +++ b/go.sum @@ -33,12 +33,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/TaoZou1/vsphere-automation-sdk-go/lib v0.5.2 h1:y70ZbnJc68vXcZagNHNLM4oltM+0DtzijV/BnTDf//Y= -github.com/TaoZou1/vsphere-automation-sdk-go/lib v0.5.2/go.mod h1:QJG7MJEjc8SJuo6p++sLJha7Sqt0tzyxjK1peikCB/E= -github.com/TaoZou1/vsphere-automation-sdk-go/runtime v0.5.2 h1:RgDmwSyqCOfMAGTfIm+A1//jzF26u5fVQhWPBixdjfc= -github.com/TaoZou1/vsphere-automation-sdk-go/runtime v0.5.2/go.mod h1:GqC85noyNzapJN4vIAO9jJ1EKVo3+jCW4/2VTaMvuSg= -github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt v0.9.3 h1:u7Qr1LRqz98ouREoRE5rZA5O4l8TtMG3NYkrZFYigBc= -github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt v0.9.3/go.mod h1:9iS9n04yKlWA4AoVU6GpvMcFB7inP1D7kMyZb5RQVdM= +github.com/TaoZou1/vsphere-automation-sdk-go/lib v0.5.4 h1:DlmdgfnJZBf3xKVlIzV8hQOoD7Sj207BuNrgJjxzM+I= +github.com/TaoZou1/vsphere-automation-sdk-go/lib v0.5.4/go.mod h1:QJG7MJEjc8SJuo6p++sLJha7Sqt0tzyxjK1peikCB/E= +github.com/TaoZou1/vsphere-automation-sdk-go/runtime v0.5.4 h1:HfPCC2OxVMZjMgfRFD6txpgynbua+roPkMT3f+SoHbA= +github.com/TaoZou1/vsphere-automation-sdk-go/runtime v0.5.4/go.mod h1:GqC85noyNzapJN4vIAO9jJ1EKVo3+jCW4/2VTaMvuSg= +github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt v0.9.8 h1:l8DRN/HDjLyTnePixRhM9Ey5U9RekYXvOcCeOwNwuRk= +github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt v0.9.8/go.mod h1:r6vXdrxnApdHWL8zxnKrWXinGoP8HEXvMVLxKcarV8c= github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt-mp v0.3.1-0.20221020082725-84e89979deb6 h1:sFObhyzxRrFCQvvZgVbWwAGyxUvhlom14Axv0Cjix90= github.com/TaoZou1/vsphere-automation-sdk-go/services/nsxt-mp v0.3.1-0.20221020082725-84e89979deb6/go.mod h1:75a9Rt4zCs3FA+pFi5HIMdNvl+5AHUDMCNblLZiErg4= github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg= @@ -51,6 +51,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= @@ -106,10 +108,11 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -174,8 +177,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -307,6 +311,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/vmware-tanzu/vm-operator/api v1.8.2 h1:7cZHVusqAmAMFWvsiU7X5xontxdjasknI/sVfe0p0Z4= +github.com/vmware-tanzu/vm-operator/api v1.8.2/go.mod h1:vauVboD3sQxP+pb28TnI9wfrj+0nH2zSEc9Q7AzWJ54= github.com/vmware/govmomi v0.27.4 h1:5kY8TAkhB20lsjzrjE073eRb8+HixBI29PVMG5lxq6I= github.com/vmware/govmomi v0.27.4/go.mod h1:daTuJEcQosNMXYJOeku0qdBJP9SOLLWB3Mqz8THtv6o= github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= @@ -368,6 +374,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -522,6 +530,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -531,6 +540,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -648,30 +659,36 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= -k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= -k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= -k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= -k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= -k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= -k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= -k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= +k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= +k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= +k8s.io/code-generator v0.26.1 h1:dusFDsnNSKlMFYhzIM0jAO1OlnTN5WYwQQ+Ai12IIlo= +k8s.io/code-generator v0.26.1/go.mod h1:OMoJ5Dqx1wgaQzKgc+ZWaZPfGjdRq/Y3WubFrZmeI3I= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.0 h1:ju2xsov5Ara6FoQuddg+az+rAxsUsTYn2IYyEKCTyDc= -sigs.k8s.io/controller-runtime v0.14.0/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= +sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 583b8380c..a39156ae3 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,2 +1,2 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ diff --git a/hack/tools.go b/hack/tools.go new file mode 100644 index 000000000..57051d8c1 --- /dev/null +++ b/hack/tools.go @@ -0,0 +1,3 @@ +package hack + +import _ "k8s.io/code-generator" diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh new file mode 100755 index 000000000..3ad782247 --- /dev/null +++ b/hack/update-codegen.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +APIS=./pkg/apis +APIS_PKG=github.com/vmware-tanzu/nsx-operator/pkg/apis +OUTPUT_PKG=github.com/vmware-tanzu/nsx-operator/pkg/client +GROUP=nsx.vmware.com + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +CODEGEN_PKG=$(go env GOMODCACHE)/k8s.io/code-generator@v0.27.1 + +rm -fr "${APIS:?}/${GROUP:?}" +rm -fr ./pkg/client + +for VERSION in v1alpha1 v1alpha2; do + mkdir -p "${APIS}/${GROUP}/${VERSION}" + cp -r "${APIS}/${VERSION}"/* "${APIS}/${GROUP}/${VERSION}/" +done + +bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ +${OUTPUT_PKG} ${APIS_PKG} \ +${GROUP}:v1alpha1,v1alpha2 \ +--go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt \ +--output-base "${SCRIPT_ROOT}" -v 10 + +mv ./${OUTPUT_PKG} ./pkg/ +cd ./pkg/client +go mod init github.com/vmware-tanzu/nsx-operator/pkg/client +go mod tidy +cd ../../ +rm -rf ./github.com \ No newline at end of file diff --git a/pkg/apis/go.mod b/pkg/apis/go.mod new file mode 100644 index 000000000..54d8b6b5b --- /dev/null +++ b/pkg/apis/go.mod @@ -0,0 +1,26 @@ +module github.com/vmware-tanzu/nsx-operator/pkg/apis + +go 1.19 + +require ( + k8s.io/api v0.26.1 + k8s.io/apimachinery v0.26.1 + sigs.k8s.io/controller-runtime v0.14.5 +) + +require ( + github.com/go-logr/logr v1.2.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + golang.org/x/net v0.13.0 // indirect + golang.org/x/text v0.11.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect +) diff --git a/pkg/apis/go.sum b/pkg/apis/go.sum new file mode 100644 index 000000000..2fd1431c8 --- /dev/null +++ b/pkg/apis/go.sum @@ -0,0 +1,86 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= +github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= +sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/condition_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/condition_types.go new file mode 100644 index 000000000..5d708b849 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/condition_types.go @@ -0,0 +1,29 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ConditionType string + +const ( + Ready ConditionType = "Ready" +) + +// Condition defines condition of custom resource. +type Condition struct { + // Type defines condition type. + Type ConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status corev1.ConditionStatus `json:"status"` + // Last time the condition transitioned from one status to another. + // This should be when the underlying condition changed. If that is not known, then using the time when + // the API field changed is acceptable. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // Reason shows a brief reason of condition. + Reason string `json:"reason,omitempty"` + // Message shows a human-readable message about condition. + Message string `json:"message,omitempty"` +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/doc.go b/pkg/apis/nsx.vmware.com/v1alpha1/doc.go new file mode 100644 index 000000000..73e7087ed --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/doc.go @@ -0,0 +1,4 @@ +// +k8s:deepcopy-gen=package +// +groupName=nsx.vmware.com + +package v1alpha1 diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/groupversion_info.go b/pkg/apis/nsx.vmware.com/v1alpha1/groupversion_info.go new file mode 100644 index 000000000..9a64fd3fb --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/groupversion_info.go @@ -0,0 +1,22 @@ +/* Copyright © 2021 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// +kubebuilder:object:generate=true +// +groupName=nsx.vmware.com +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "nsx.vmware.com", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/ippool_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/ippool_types.go new file mode 100644 index 000000000..6620529b0 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/ippool_types.go @@ -0,0 +1,73 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// IPPool is the Schema for the ippools API. +type IPPool struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec IPPoolSpec `json:"spec"` + Status IPPoolStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// IPPoolList contains a list of IPPool. +type IPPoolList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IPPool `json:"items"` +} + +// IPPoolSpec defines the desired state of IPPool. +type IPPoolSpec struct { + // Subnets defines set of subnets need to be allocated. + // +optional + Subnets []SubnetRequest `json:"subnets"` +} + +// IPPoolStatus defines the observed state of IPPool. +type IPPoolStatus struct { + // Subnets defines subnets allocation result. + Subnets []SubnetResult `json:"subnets"` + // Conditions defines current state of the IPPool. + Conditions []Condition `json:"conditions"` +} + +// SubnetRequest defines the subnet allocation request. +type SubnetRequest struct { + // PrefixLength defines prefix length for this subnet. + PrefixLength int `json:"prefixLength,omitempty"` + + // IPFamily defines the IP family type for this subnet, could be IPv4 or IPv6. + // This is optional, the default is IPv4. + // +kubebuilder:validation:Enum=IPv4;IPv6 + // +kubebuilder:default=IPv4 + IPFamily string `json:"ipFamily,omitempty"` + + // Name defines the name of this subnet. + Name string `json:"name"` +} + +// SubnetResult defines the subnet allocation result. +type SubnetResult struct { + // CIDR defines the allocated CIDR. + CIDR string `json:"cidr"` + + // Name defines the name of this subnet. + Name string `json:"name"` +} + +func init() { + SchemeBuilder.Register(&IPPool{}, &IPPoolList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/nsxserviceaccount_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/nsxserviceaccount_types.go new file mode 100644 index 000000000..3a32bd809 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/nsxserviceaccount_types.go @@ -0,0 +1,101 @@ +/* Copyright © 2022 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NSXServiceAccountSpec defines the desired state of NSXServiceAccount +type NSXServiceAccountSpec struct { + VPCName string `json:"vpcName,omitempty"` + // EnableCertRotation enables cert rotation feature in this cluster when NSXT >=4.1.3 + EnableCertRotation bool `json:"enableCertRotation,omitempty"` +} + +type NSXProxyEndpointAddress struct { + Hostname string `json:"hostname,omitempty"` + //+kubebuilder:validation:Format=ip + IP string `json:"ip,omitempty"` +} + +type NSXProxyProtocol string + +const ( + NSXProxyProtocolTCP NSXProxyProtocol = "TCP" +) + +type NSXProxyEndpointPort struct { + Name string `json:"name,omitempty"` + Port uint16 `json:"port,omitempty"` + Protocol NSXProxyProtocol `json:"protocol,omitempty"` +} + +type NSXProxyEndpoint struct { + Addresses []NSXProxyEndpointAddress `json:"addresses,omitempty"` + Ports []NSXProxyEndpointPort `json:"ports,omitempty"` +} + +type NSXSecret struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type NSXServiceAccountPhase string + +const ( + NSXServiceAccountPhaseRealized NSXServiceAccountPhase = "realized" + NSXServiceAccountPhaseInProgress NSXServiceAccountPhase = "inProgress" + NSXServiceAccountPhaseFailed NSXServiceAccountPhase = "failed" + + ConditionTypeRealized string = "Realized" + ConditionReasonRealizationSuccess string = "RealizationSuccess" + ConditionReasonRealizationError string = "RealizationError" +) + +// NSXServiceAccountStatus defines the observed state of NSXServiceAccount +type NSXServiceAccountStatus struct { + // Deprecated: Use Conditions instead. + // +kubebuilder:deprecatedversion:warning="nsx.vmware.com/v1alpha1 Phase is deprecated" + Phase NSXServiceAccountPhase `json:"phase,omitempty"` + // Deprecated: Use Conditions instead. + // +kubebuilder:deprecatedversion:warning="nsx.vmware.com/v1alpha1 Reason is deprecated" + Reason string `json:"reason,omitempty"` + // Represents the realization status of a NSXServiceAccount's current state. + // Known .status.conditions.type is: "Realized" + Conditions []metav1.Condition `json:"conditions,omitempty"` + VPCPath string `json:"vpcPath,omitempty"` + NSXManagers []string `json:"nsxManagers,omitempty"` + ProxyEndpoints NSXProxyEndpoint `json:"proxyEndpoints,omitempty"` + ClusterID string `json:"clusterID,omitempty"` + ClusterName string `json:"clusterName,omitempty"` + Secrets []NSXSecret `json:"secrets,omitempty"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// NSXServiceAccount is the Schema for the nsxserviceaccounts API +type NSXServiceAccount struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NSXServiceAccountSpec `json:"spec,omitempty"` + Status NSXServiceAccountStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// NSXServiceAccountList contains a list of NSXServiceAccount +type NSXServiceAccountList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NSXServiceAccount `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NSXServiceAccount{}, &NSXServiceAccountList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/register.go b/pkg/apis/nsx.vmware.com/v1alpha1/register.go new file mode 100644 index 000000000..2dfd631eb --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/register.go @@ -0,0 +1,12 @@ +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = GroupVersion + +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/securitypolicy_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/securitypolicy_types.go new file mode 100644 index 000000000..246965a80 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/securitypolicy_types.go @@ -0,0 +1,141 @@ +/* Copyright © 2021 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// +kubebuilder:object:generate=true +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// RuleAction describes the action to be applied on traffic matching a rule. +type RuleAction string + +const ( + // RuleActionAllow describes that the traffic matching the rule must be allowed. + RuleActionAllow RuleAction = "Allow" + // RuleActionDrop describes that the traffic matching the rule must be dropped. + RuleActionDrop RuleAction = "Drop" + // RuleActionReject indicates that the traffic matching the rule must be rejected and the + // client will receive a response. + RuleActionReject RuleAction = "Reject" +) + +// RuleDirection specifies the direction of traffic. +type RuleDirection string + +const ( + // RuleDirectionIn specifies that the direction of traffic must be ingress, equivalent to "Ingress". + RuleDirectionIn RuleDirection = "In" + // RuleDirectionIngress specifies that the direction of traffic must be ingress, equivalent to "In". + RuleDirectionIngress RuleDirection = "Ingress" + // RuleDirectionOut specifies that the direction of traffic must be egress, equivalent to "Egress". + RuleDirectionOut RuleDirection = "Out" + // RuleDirectionEgress specifies that the direction of traffic must be egress, equivalent to "Out". + RuleDirectionEgress RuleDirection = "Egress" +) + +// SecurityPolicySpec defines the desired state of SecurityPolicy. +type SecurityPolicySpec struct { + // Priority defines the order of policy enforcement. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int `json:"priority,omitempty"` + // AppliedTo is a list of policy targets to apply rules. + // Policy level 'Applied To' will take precedence over rule level. + AppliedTo []SecurityPolicyTarget `json:"appliedTo,omitempty"` + // Rules is a list of policy rules. + Rules []SecurityPolicyRule `json:"rules,omitempty"` +} + +// SecurityPolicyRule defines a rule of SecurityPolicy. +type SecurityPolicyRule struct { + // Action specifies the action to be applied on the rule. + Action *RuleAction `json:"action"` + // AppliedTo is a list of rule targets. + // Policy level 'Applied To' will take precedence over rule level. + AppliedTo []SecurityPolicyTarget `json:"appliedTo,omitempty"` + // Direction is the direction of the rule, including 'In' or 'Ingress', 'Out' or 'Egress'. + Direction *RuleDirection `json:"direction"` + // Sources defines the endpoints where the traffic is from. For ingress rule only. + Sources []SecurityPolicyPeer `json:"sources,omitempty"` + // Destinations defines the endpoints where the traffic is to. For egress rule only. + Destinations []SecurityPolicyPeer `json:"destinations,omitempty"` + // Ports is a list of ports to be matched. + Ports []SecurityPolicyPort `json:"ports,omitempty"` + // Name is the display name of this rule. + Name string `json:"name,omitempty"` +} + +// SecurityPolicyTarget defines the target endpoints to apply SecurityPolicy. +type SecurityPolicyTarget struct { + // VMSelector uses label selector to select VMs. + VMSelector *metav1.LabelSelector `json:"vmSelector,omitempty"` + // PodSelector uses label selector to select Pods. + PodSelector *metav1.LabelSelector `json:"podSelector,omitempty"` +} + +// SecurityPolicyPeer defines the source or destination of traffic. +type SecurityPolicyPeer struct { + // VMSelector uses label selector to select VMs. + VMSelector *metav1.LabelSelector `json:"vmSelector,omitempty"` + // PodSelector uses label selector to select Pods. + PodSelector *metav1.LabelSelector `json:"podSelector,omitempty"` + // NamespaceSelector uses label selector to select Namespaces. + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` + // IPBlocks is a list of IP CIDRs. + IPBlocks []IPBlock `json:"ipBlocks,omitempty"` +} + +// IPBlock describes a particular CIDR that is allowed or denied to/from the workloads matched by an AppliedTo. +type IPBlock struct { + // CIDR is a string representing the IP Block. + // A valid example is "192.168.1.1/24". + CIDR string `json:"cidr"` +} + +// SecurityPolicyPort describes protocol and ports for traffic. +type SecurityPolicyPort struct { + // Protocol(TCP, UDP) is the protocol to match traffic. + // It is TCP by default. + Protocol corev1.Protocol `json:"protocol,omitempty"` + // Port is the name or port number. + Port intstr.IntOrString `json:"port,omitempty"` + // EndPort defines the end of port range. + EndPort int `json:"endPort,omitempty"` +} + +// SecurityPolicyStatus defines the observed state of SecurityPolicy. +type SecurityPolicyStatus struct { + // Conditions describes current state of security policy. + Conditions []Condition `json:"conditions"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// SecurityPolicy is the Schema for the securitypolicies API. +type SecurityPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SecurityPolicySpec `json:"spec"` + Status SecurityPolicyStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SecurityPolicyList contains a list of SecurityPolicy. +type SecurityPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SecurityPolicy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SecurityPolicy{}, &SecurityPolicyList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/staticroute_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/staticroute_types.go new file mode 100644 index 000000000..87d6b7b80 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/staticroute_types.go @@ -0,0 +1,65 @@ +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type StaticRouteStatusCondition string + +// StaticRouteCondition defines condition of StaticRoute. +type StaticRouteCondition Condition + +// StaticRouteSpec defines static routes configuration on VPC. +type StaticRouteSpec struct { + // Specify network address in CIDR format. + // +kubebuilder:validation:Format=cidr + Network string `json:"network"` + // Next hop gateway + // +kubebuilder:validation:MinItems=1 + NextHops []NextHop `json:"nextHops"` +} + +// NextHop defines next hop configuration for network. +type NextHop struct { + // Next hop gateway IP address. + // +kubebuilder:validation:Format=ip + IPAddress string `json:"ipAddress"` +} + +// StaticRouteStatus defines the observed state of StaticRoute. +type StaticRouteStatus struct { + Conditions []StaticRouteCondition `json:"conditions"` + NSXResourcePath string `json:"nsxResourcePath"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// StaticRoute is the Schema for the staticroutes API. +// +kubebuilder:printcolumn:name="Network",type=string,JSONPath=`.spec.network`,description="Network in CIDR format" +// +kubebuilder:printcolumn:name="NextHops",type=string,JSONPath=`.spec.nextHops[*].ipAddress`,description="Next Hops" +type StaticRoute struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec StaticRouteSpec `json:"spec,omitempty"` + Status StaticRouteStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// StaticRouteList contains a list of StaticRoute. +type StaticRouteList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []StaticRoute `json:"items"` +} + +func init() { + SchemeBuilder.Register(&StaticRoute{}, &StaticRouteList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/subnet_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/subnet_types.go new file mode 100644 index 000000000..5473a8021 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/subnet_types.go @@ -0,0 +1,104 @@ +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type AccessMode string + +// SubnetSpec defines the desired state of Subnet. +type SubnetSpec struct { + // Size of Subnet based upon estimated workload count. + // +kubebuilder:validation:Maximum:=65536 + // +kubebuilder:validation:Minimum:=16 + IPv4SubnetSize int `json:"ipv4SubnetSize,omitempty"` + // Access mode of Subnet, accessible only from within VPC or from outside VPC. + // +kubebuilder:validation:Enum=Private;Public + AccessMode AccessMode `json:"accessMode,omitempty"` + // Subnet CIDRS. + // +kubebuilder:validation:MinItems=0 + // +kubebuilder:validation:MaxItems=2 + IPAddresses []string `json:"ipAddresses,omitempty"` + // Subnet advanced configuration. + AdvancedConfig AdvancedConfig `json:"advancedConfig,omitempty"` + // DHCPConfig DHCP configuration. + DHCPConfig DHCPConfig `json:"DHCPConfig,omitempty"` +} + +// SubnetStatus defines the observed state of Subnet. +type SubnetStatus struct { + NSXResourcePath string `json:"nsxResourcePath,omitempty"` + IPAddresses []string `json:"ipAddresses,omitempty"` + Conditions []Condition `json:"conditions,omitempty"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// Subnet is the Schema for the subnets API. +// +kubebuilder:printcolumn:name="AccessMode",type=string,JSONPath=`.spec.accessMode`,description="Access mode of Subnet" +// +kubebuilder:printcolumn:name="IPv4SubnetSize",type=string,JSONPath=`.spec.ipv4SubnetSize`,description="Size of Subnet" +// +kubebuilder:printcolumn:name="IPAddresses",type=string,JSONPath=`.status.ipAddresses[*]`,description="CIDRs for the Subnet" +type Subnet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SubnetSpec `json:"spec,omitempty"` + Status SubnetStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SubnetList contains a list of Subnet. +type SubnetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Subnet `json:"items"` +} + +// AdvancedConfig is Subnet advanced configuration. +type AdvancedConfig struct { + // StaticIPAllocation configuration for subnet ports with VIF attachment. + StaticIPAllocation StaticIPAllocation `json:"staticIPAllocation,omitempty"` +} + +// StaticIPAllocation is static IP allocation for subnet ports with VIF attachment. +type StaticIPAllocation struct { + // Enable or disable static IP allocation for subnet ports with VIF attachment. + // +kubebuilder:default:=false + Enable bool `json:"enable,omitempty"` +} + +// DHCPConfig is DHCP configuration. +type DHCPConfig struct { + // +kubebuilder:default:=false + EnableDHCP bool `json:"enableDHCP,omitempty"` + // DHCPRelayConfigPath is policy path of DHCP-relay-config. + DHCPRelayConfigPath string `json:"dhcpRelayConfigPath,omitempty"` + // DHCPV4PoolSize IPs in % to be reserved for DHCP ranges. + // By default, 80% of IPv4 IPs will be reserved for DHCP. + // Configure 0 if no pool is required. + // +kubebuilder:default:=80 + // +kubebuilder:validation:Maximum:=100 + // +kubebuilder:validation:Minimum:=0 + DHCPV4PoolSize int `json:"dhcpV4PoolSize,omitempty"` + // DHCPV6PoolSize number of IPs to be reserved for DHCP ranges. + // By default, 2000 IPv6 IPs will be reserved for DHCP. + // +kubebuilder:default:=2000 + DHCPV6PoolSize int `json:"dhcpV6PoolSize,omitempty"` + DNSClientConfig DNSClientConfig `json:"dnsClientConfig,omitempty"` +} + +// DNSClientConfig holds DNS configurations. +type DNSClientConfig struct { + DNSServersIPs []string `json:"dnsServersIPs,omitempty"` +} + +func init() { + SchemeBuilder.Register(&Subnet{}, &SubnetList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/subnetport_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/subnetport_types.go new file mode 100644 index 000000000..d395692f7 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/subnetport_types.go @@ -0,0 +1,69 @@ +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SubnetPortSpec defines the desired state of SubnetPort. +type SubnetPortSpec struct { + // Subnet defines the parent Subnet name of the SubnetPort. + Subnet string `json:"subnet,omitempty"` + // SubnetSet defines the parent SubnetSet name of the SubnetPort. + SubnetSet string `json:"subnetSet,omitempty"` + // AttachmentRef refers to the virtual machine which the SubnetPort is attached. + AttachmentRef corev1.ObjectReference `json:"attachmentRef,omitempty"` +} + +// SubnetPortStatus defines the observed state of SubnetPort. +type SubnetPortStatus struct { + // Conditions describes current state of SubnetPort. + Conditions []Condition `json:"conditions,omitempty"` + // VIFID describes the attachment VIF ID owned by the SubnetPort in NSX-T. + VIFID string `json:"vifID,omitempty"` + // IPAddresses describes the IP addresses of the SubnetPort. + IPAddresses []SubnetPortIPAddress `json:"ipAddresses,omitempty"` + // MACAddress describes the MAC address of the SubnetPort. + MACAddress string `json:"macAddress,omitempty"` + // LogicalSwitchID defines the logical switch ID in NSX-T. + LogicalSwitchID string `json:"logicalSwitchID,omitempty"` +} + +type SubnetPortIPAddress struct { + Gateway string `json:"gateway,omitempty"` + IP string `json:"ip,omitempty"` + Netmask string `json:"netmask,omitempty"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// SubnetPort is the Schema for the subnetports API. +// +kubebuilder:printcolumn:name="VIFID",type=string,JSONPath=`.status.vifID`,description="Attachment VIF ID owned by the SubnetPort" +// +kubebuilder:printcolumn:name="IPAddress",type=string,JSONPath=`.status.ipAddresses[0].ip`,description="IP Address of the SubnetPort" +// +kubebuilder:printcolumn:name="MACAddress",type=string,JSONPath=`.status.macAddress`,description="MAC Address of the SubnetPort" +type SubnetPort struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SubnetPortSpec `json:"spec,omitempty"` + Status SubnetPortStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SubnetPortList contains a list of SubnetPort. +type SubnetPortList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SubnetPort `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SubnetPort{}, &SubnetPortList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/subnetset_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/subnetset_types.go new file mode 100644 index 000000000..5c6864893 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/subnetset_types.go @@ -0,0 +1,65 @@ +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SubnetSetSpec defines the desired state of SubnetSet. +type SubnetSetSpec struct { + // Size of Subnet based upon estimated workload count. + // +kubebuilder:validation:Maximum:=65536 + // +kubebuilder:validation:Minimum:=16 + IPv4SubnetSize int `json:"ipv4SubnetSize,omitempty"` + // Access mode of Subnet, accessible only from within VPC or from outside VPC. + // +kubebuilder:validation:Enum=Private;Public + AccessMode AccessMode `json:"accessMode,omitempty"` + // Subnet advanced configuration. + AdvancedConfig AdvancedConfig `json:"advancedConfig,omitempty"` + // DHCPConfig DHCP configuration. + DHCPConfig DHCPConfig `json:"DHCPConfig,omitempty"` +} + +// SubnetInfo defines the observed state of a single Subnet of a SubnetSet. +type SubnetInfo struct { + NSXResourcePath string `json:"nsxResourcePath"` + IPAddresses []string `json:"ipAddresses"` +} + +// SubnetSetStatus defines the observed state of SubnetSet. +type SubnetSetStatus struct { + Conditions []Condition `json:"conditions,omitempty"` + Subnets []SubnetInfo `json:"subnets,omitempty"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// SubnetSet is the Schema for the subnetsets API. +// +kubebuilder:printcolumn:name="AccessMode",type=string,JSONPath=`.spec.accessMode`,description="Access mode of Subnet" +// +kubebuilder:printcolumn:name="IPv4SubnetSize",type=string,JSONPath=`.spec.ipv4SubnetSize`,description="Size of Subnet" +// +kubebuilder:printcolumn:name="IPAddresses",type=string,JSONPath=`.status.subnets[*].ipAddresses[*]`,description="CIDRs for the Subnet" +type SubnetSet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SubnetSetSpec `json:"spec,omitempty"` + Status SubnetSetStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// SubnetSetList contains a list of SubnetSet. +type SubnetSetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SubnetSet `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SubnetSet{}, &SubnetSetList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/vpc_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/vpc_types.go new file mode 100644 index 000000000..a552df36e --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/vpc_types.go @@ -0,0 +1,57 @@ +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// VPC is the Schema for the VPC API +// +kubebuilder:printcolumn:name="PrivateIPv4CIDRs",type=string,JSONPath=`.status.privateIPv4CIDRs`,description="Private IPv4 CIDRs" +// +kubebuilder:printcolumn:name="SNATIP",type=string,JSONPath=`.status.defaultSNATIP`,description="Default SNAT IP for Private Subnets" +// +kubebuilder:printcolumn:name="LBSubnetCIDR",type=string,JSONPath=`.status.lbSubnetCIDR`,description="CIDR for the load balancer Subnet" +type VPC struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VPCSpec `json:"spec,omitempty"` + Status VPCStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// VPCList contains a list of VPC +type VPCList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VPC `json:"items"` +} + +// VPCSpec defines VPC configuration +type VPCSpec struct { +} + +// VPCStatus defines the observed state of VPC +type VPCStatus struct { + Conditions []Condition `json:"conditions"` + // NSX VPC Policy API resource path. + NSXResourcePath string `json:"nsxResourcePath"` + // Default SNAT IP for Private Subnets. + DefaultSNATIP string `json:"defaultSNATIP"` + // NSX PolicyPath for the load balancer Subnet. + LBSubnetPath string `json:"lbSubnetPath"` + // CIDR for the load balancer Subnet. + LBSubnetCIDR string `json:"lbSubnetCIDR"` + // Private CIDRs used for the VPC. + PrivateIPv4CIDRs []string `json:"privateIPv4CIDRs"` +} + +func init() { + SchemeBuilder.Register(&VPC{}, &VPCList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration_types.go b/pkg/apis/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration_types.go new file mode 100644 index 000000000..2571b800f --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration_types.go @@ -0,0 +1,81 @@ +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// +kubebuilder:object:generate=true +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + AccessModePublic string = "Public" + AccessModePrivate string = "Private" +) + +// VPCNetworkConfigurationSpec defines the desired state of VPCNetworkConfiguration. +// There is a default VPCNetworkConfiguration that applies to Namespaces +// do not have a VPCNetworkConfiguration assigned. When a field is not set +// in a Namespace's VPCNetworkConfiguration, the Namespace will use the value +// in the default VPCNetworkConfiguration. +type VPCNetworkConfigurationSpec struct { + // PolicyPath of Tier0 or Tier0 VRF gateway. + DefaultGatewayPath string `json:"defaultGatewayPath,omitempty"` + // Edge cluster path on which the networking elements will be created. + EdgeClusterPath string `json:"edgeClusterPath,omitempty"` + // NSX-T Project the Namespace associated with. + NSXTProject string `json:"nsxtProject,omitempty"` + // NSX-T IPv4 Block paths used to allocate external Subnets. + // +kubebuilder:validation:MinItems=0 + // +kubebuilder:validation:MaxItems=5 + ExternalIPv4Blocks []string `json:"externalIPv4Blocks,omitempty"` + // Private IPv4 CIDRs used to allocate Private Subnets. + // +kubebuilder:validation:MinItems=0 + // +kubebuilder:validation:MaxItems=5 + PrivateIPv4CIDRs []string `json:"privateIPv4CIDRs,omitempty"` + // Default size of Subnet based upon estimated workload count. + // Defaults to 26. + // +kubebuilder:default=26 + DefaultIPv4SubnetSize int `json:"defaultIPv4SubnetSize,omitempty"` + // DefaultSubnetAccessMode defines the access mode of the default SubnetSet for PodVM and VM. + // Must be Public or Private. + // +kubebuilder:validation:Enum=Public;Private + DefaultSubnetAccessMode string `json:"defaultSubnetAccessMode,omitempty"` +} + +// VPCNetworkConfigurationStatus defines the observed state of VPCNetworkConfiguration +type VPCNetworkConfigurationStatus struct { + // Conditions describes current state of VPCNetworkConfiguration. + Conditions []Condition `json:"conditions"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// VPCNetworkConfiguration is the Schema for the vpcnetworkconfigurations API. +// +kubebuilder:resource:scope="Cluster" +// +kubebuilder:printcolumn:name="NSXTProject",type=string,JSONPath=`.spec.nsxtProject`,description="NSXTProject the Namespace associated with" +// +kubebuilder:printcolumn:name="ExternalIPv4Blocks",type=string,JSONPath=`.spec.externalIPv4Blocks`,description="ExternalIPv4Blocks assigned to the Namespace" +// +kubebuilder:printcolumn:name="PrivateIPv4CIDRs",type=string,JSONPath=`.spec.privateIPv4CIDRs`,description="PrivateIPv4CIDRs assigned to the Namespace" +type VPCNetworkConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VPCNetworkConfigurationSpec `json:"spec,omitempty"` + Status VPCNetworkConfigurationStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// VPCNetworkConfigurationList contains a list of VPCNetworkConfiguration. +type VPCNetworkConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VPCNetworkConfiguration `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VPCNetworkConfiguration{}, &VPCNetworkConfigurationList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/nsx.vmware.com/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..2d5e262e6 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,1351 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdvancedConfig) DeepCopyInto(out *AdvancedConfig) { + *out = *in + out.StaticIPAllocation = in.StaticIPAllocation +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdvancedConfig. +func (in *AdvancedConfig) DeepCopy() *AdvancedConfig { + if in == nil { + return nil + } + out := new(AdvancedConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DHCPConfig) DeepCopyInto(out *DHCPConfig) { + *out = *in + in.DNSClientConfig.DeepCopyInto(&out.DNSClientConfig) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCPConfig. +func (in *DHCPConfig) DeepCopy() *DHCPConfig { + if in == nil { + return nil + } + out := new(DHCPConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSClientConfig) DeepCopyInto(out *DNSClientConfig) { + *out = *in + if in.DNSServersIPs != nil { + in, out := &in.DNSServersIPs, &out.DNSServersIPs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSClientConfig. +func (in *DNSClientConfig) DeepCopy() *DNSClientConfig { + if in == nil { + return nil + } + out := new(DNSClientConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPBlock) DeepCopyInto(out *IPBlock) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPBlock. +func (in *IPBlock) DeepCopy() *IPBlock { + if in == nil { + return nil + } + out := new(IPBlock) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPool) DeepCopyInto(out *IPPool) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPool. +func (in *IPPool) DeepCopy() *IPPool { + if in == nil { + return nil + } + out := new(IPPool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPPool) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolList) DeepCopyInto(out *IPPoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IPPool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolList. +func (in *IPPoolList) DeepCopy() *IPPoolList { + if in == nil { + return nil + } + out := new(IPPoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPPoolList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolSpec) DeepCopyInto(out *IPPoolSpec) { + *out = *in + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetRequest, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolSpec. +func (in *IPPoolSpec) DeepCopy() *IPPoolSpec { + if in == nil { + return nil + } + out := new(IPPoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolStatus) DeepCopyInto(out *IPPoolStatus) { + *out = *in + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetResult, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolStatus. +func (in *IPPoolStatus) DeepCopy() *IPPoolStatus { + if in == nil { + return nil + } + out := new(IPPoolStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXProxyEndpoint) DeepCopyInto(out *NSXProxyEndpoint) { + *out = *in + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]NSXProxyEndpointAddress, len(*in)) + copy(*out, *in) + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]NSXProxyEndpointPort, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXProxyEndpoint. +func (in *NSXProxyEndpoint) DeepCopy() *NSXProxyEndpoint { + if in == nil { + return nil + } + out := new(NSXProxyEndpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXProxyEndpointAddress) DeepCopyInto(out *NSXProxyEndpointAddress) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXProxyEndpointAddress. +func (in *NSXProxyEndpointAddress) DeepCopy() *NSXProxyEndpointAddress { + if in == nil { + return nil + } + out := new(NSXProxyEndpointAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXProxyEndpointPort) DeepCopyInto(out *NSXProxyEndpointPort) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXProxyEndpointPort. +func (in *NSXProxyEndpointPort) DeepCopy() *NSXProxyEndpointPort { + if in == nil { + return nil + } + out := new(NSXProxyEndpointPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXSecret) DeepCopyInto(out *NSXSecret) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXSecret. +func (in *NSXSecret) DeepCopy() *NSXSecret { + if in == nil { + return nil + } + out := new(NSXSecret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXServiceAccount) DeepCopyInto(out *NSXServiceAccount) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXServiceAccount. +func (in *NSXServiceAccount) DeepCopy() *NSXServiceAccount { + if in == nil { + return nil + } + out := new(NSXServiceAccount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NSXServiceAccount) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXServiceAccountList) DeepCopyInto(out *NSXServiceAccountList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NSXServiceAccount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXServiceAccountList. +func (in *NSXServiceAccountList) DeepCopy() *NSXServiceAccountList { + if in == nil { + return nil + } + out := new(NSXServiceAccountList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NSXServiceAccountList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXServiceAccountSpec) DeepCopyInto(out *NSXServiceAccountSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXServiceAccountSpec. +func (in *NSXServiceAccountSpec) DeepCopy() *NSXServiceAccountSpec { + if in == nil { + return nil + } + out := new(NSXServiceAccountSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NSXServiceAccountStatus) DeepCopyInto(out *NSXServiceAccountStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NSXManagers != nil { + in, out := &in.NSXManagers, &out.NSXManagers + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.ProxyEndpoints.DeepCopyInto(&out.ProxyEndpoints) + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]NSXSecret, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NSXServiceAccountStatus. +func (in *NSXServiceAccountStatus) DeepCopy() *NSXServiceAccountStatus { + if in == nil { + return nil + } + out := new(NSXServiceAccountStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NextHop) DeepCopyInto(out *NextHop) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NextHop. +func (in *NextHop) DeepCopy() *NextHop { + if in == nil { + return nil + } + out := new(NextHop) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicy) DeepCopyInto(out *SecurityPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicy. +func (in *SecurityPolicy) DeepCopy() *SecurityPolicy { + if in == nil { + return nil + } + out := new(SecurityPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SecurityPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyList) DeepCopyInto(out *SecurityPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SecurityPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyList. +func (in *SecurityPolicyList) DeepCopy() *SecurityPolicyList { + if in == nil { + return nil + } + out := new(SecurityPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SecurityPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyPeer) DeepCopyInto(out *SecurityPolicyPeer) { + *out = *in + if in.VMSelector != nil { + in, out := &in.VMSelector, &out.VMSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.PodSelector != nil { + in, out := &in.PodSelector, &out.PodSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.IPBlocks != nil { + in, out := &in.IPBlocks, &out.IPBlocks + *out = make([]IPBlock, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyPeer. +func (in *SecurityPolicyPeer) DeepCopy() *SecurityPolicyPeer { + if in == nil { + return nil + } + out := new(SecurityPolicyPeer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyPort) DeepCopyInto(out *SecurityPolicyPort) { + *out = *in + out.Port = in.Port +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyPort. +func (in *SecurityPolicyPort) DeepCopy() *SecurityPolicyPort { + if in == nil { + return nil + } + out := new(SecurityPolicyPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyRule) DeepCopyInto(out *SecurityPolicyRule) { + *out = *in + if in.Action != nil { + in, out := &in.Action, &out.Action + *out = new(RuleAction) + **out = **in + } + if in.AppliedTo != nil { + in, out := &in.AppliedTo, &out.AppliedTo + *out = make([]SecurityPolicyTarget, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Direction != nil { + in, out := &in.Direction, &out.Direction + *out = new(RuleDirection) + **out = **in + } + if in.Sources != nil { + in, out := &in.Sources, &out.Sources + *out = make([]SecurityPolicyPeer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Destinations != nil { + in, out := &in.Destinations, &out.Destinations + *out = make([]SecurityPolicyPeer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]SecurityPolicyPort, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyRule. +func (in *SecurityPolicyRule) DeepCopy() *SecurityPolicyRule { + if in == nil { + return nil + } + out := new(SecurityPolicyRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicySpec) DeepCopyInto(out *SecurityPolicySpec) { + *out = *in + if in.AppliedTo != nil { + in, out := &in.AppliedTo, &out.AppliedTo + *out = make([]SecurityPolicyTarget, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]SecurityPolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicySpec. +func (in *SecurityPolicySpec) DeepCopy() *SecurityPolicySpec { + if in == nil { + return nil + } + out := new(SecurityPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyStatus) DeepCopyInto(out *SecurityPolicyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyStatus. +func (in *SecurityPolicyStatus) DeepCopy() *SecurityPolicyStatus { + if in == nil { + return nil + } + out := new(SecurityPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyTarget) DeepCopyInto(out *SecurityPolicyTarget) { + *out = *in + if in.VMSelector != nil { + in, out := &in.VMSelector, &out.VMSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.PodSelector != nil { + in, out := &in.PodSelector, &out.PodSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyTarget. +func (in *SecurityPolicyTarget) DeepCopy() *SecurityPolicyTarget { + if in == nil { + return nil + } + out := new(SecurityPolicyTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticIPAllocation) DeepCopyInto(out *StaticIPAllocation) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticIPAllocation. +func (in *StaticIPAllocation) DeepCopy() *StaticIPAllocation { + if in == nil { + return nil + } + out := new(StaticIPAllocation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticRoute) DeepCopyInto(out *StaticRoute) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticRoute. +func (in *StaticRoute) DeepCopy() *StaticRoute { + if in == nil { + return nil + } + out := new(StaticRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StaticRoute) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticRouteCondition) DeepCopyInto(out *StaticRouteCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticRouteCondition. +func (in *StaticRouteCondition) DeepCopy() *StaticRouteCondition { + if in == nil { + return nil + } + out := new(StaticRouteCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticRouteList) DeepCopyInto(out *StaticRouteList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StaticRoute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticRouteList. +func (in *StaticRouteList) DeepCopy() *StaticRouteList { + if in == nil { + return nil + } + out := new(StaticRouteList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StaticRouteList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticRouteSpec) DeepCopyInto(out *StaticRouteSpec) { + *out = *in + if in.NextHops != nil { + in, out := &in.NextHops, &out.NextHops + *out = make([]NextHop, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticRouteSpec. +func (in *StaticRouteSpec) DeepCopy() *StaticRouteSpec { + if in == nil { + return nil + } + out := new(StaticRouteSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StaticRouteStatus) DeepCopyInto(out *StaticRouteStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]StaticRouteCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticRouteStatus. +func (in *StaticRouteStatus) DeepCopy() *StaticRouteStatus { + if in == nil { + return nil + } + out := new(StaticRouteStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subnet) DeepCopyInto(out *Subnet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subnet. +func (in *Subnet) DeepCopy() *Subnet { + if in == nil { + return nil + } + out := new(Subnet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Subnet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetInfo) DeepCopyInto(out *SubnetInfo) { + *out = *in + if in.IPAddresses != nil { + in, out := &in.IPAddresses, &out.IPAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetInfo. +func (in *SubnetInfo) DeepCopy() *SubnetInfo { + if in == nil { + return nil + } + out := new(SubnetInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetList) DeepCopyInto(out *SubnetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Subnet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetList. +func (in *SubnetList) DeepCopy() *SubnetList { + if in == nil { + return nil + } + out := new(SubnetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPort) DeepCopyInto(out *SubnetPort) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPort. +func (in *SubnetPort) DeepCopy() *SubnetPort { + if in == nil { + return nil + } + out := new(SubnetPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetPort) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPortIPAddress) DeepCopyInto(out *SubnetPortIPAddress) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPortIPAddress. +func (in *SubnetPortIPAddress) DeepCopy() *SubnetPortIPAddress { + if in == nil { + return nil + } + out := new(SubnetPortIPAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPortList) DeepCopyInto(out *SubnetPortList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SubnetPort, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPortList. +func (in *SubnetPortList) DeepCopy() *SubnetPortList { + if in == nil { + return nil + } + out := new(SubnetPortList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetPortList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPortSpec) DeepCopyInto(out *SubnetPortSpec) { + *out = *in + out.AttachmentRef = in.AttachmentRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPortSpec. +func (in *SubnetPortSpec) DeepCopy() *SubnetPortSpec { + if in == nil { + return nil + } + out := new(SubnetPortSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPortStatus) DeepCopyInto(out *SubnetPortStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.IPAddresses != nil { + in, out := &in.IPAddresses, &out.IPAddresses + *out = make([]SubnetPortIPAddress, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPortStatus. +func (in *SubnetPortStatus) DeepCopy() *SubnetPortStatus { + if in == nil { + return nil + } + out := new(SubnetPortStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetRequest) DeepCopyInto(out *SubnetRequest) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetRequest. +func (in *SubnetRequest) DeepCopy() *SubnetRequest { + if in == nil { + return nil + } + out := new(SubnetRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetResult) DeepCopyInto(out *SubnetResult) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetResult. +func (in *SubnetResult) DeepCopy() *SubnetResult { + if in == nil { + return nil + } + out := new(SubnetResult) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetSet) DeepCopyInto(out *SubnetSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSet. +func (in *SubnetSet) DeepCopy() *SubnetSet { + if in == nil { + return nil + } + out := new(SubnetSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetSetList) DeepCopyInto(out *SubnetSetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SubnetSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSetList. +func (in *SubnetSetList) DeepCopy() *SubnetSetList { + if in == nil { + return nil + } + out := new(SubnetSetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetSetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetSetSpec) DeepCopyInto(out *SubnetSetSpec) { + *out = *in + out.AdvancedConfig = in.AdvancedConfig + in.DHCPConfig.DeepCopyInto(&out.DHCPConfig) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSetSpec. +func (in *SubnetSetSpec) DeepCopy() *SubnetSetSpec { + if in == nil { + return nil + } + out := new(SubnetSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetSetStatus) DeepCopyInto(out *SubnetSetStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetInfo, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSetStatus. +func (in *SubnetSetStatus) DeepCopy() *SubnetSetStatus { + if in == nil { + return nil + } + out := new(SubnetSetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetSpec) DeepCopyInto(out *SubnetSpec) { + *out = *in + if in.IPAddresses != nil { + in, out := &in.IPAddresses, &out.IPAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.AdvancedConfig = in.AdvancedConfig + in.DHCPConfig.DeepCopyInto(&out.DHCPConfig) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSpec. +func (in *SubnetSpec) DeepCopy() *SubnetSpec { + if in == nil { + return nil + } + out := new(SubnetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetStatus) DeepCopyInto(out *SubnetStatus) { + *out = *in + if in.IPAddresses != nil { + in, out := &in.IPAddresses, &out.IPAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetStatus. +func (in *SubnetStatus) DeepCopy() *SubnetStatus { + if in == nil { + return nil + } + out := new(SubnetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPC) DeepCopyInto(out *VPC) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPC. +func (in *VPC) DeepCopy() *VPC { + if in == nil { + return nil + } + out := new(VPC) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VPC) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCList) DeepCopyInto(out *VPCList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VPC, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCList. +func (in *VPCList) DeepCopy() *VPCList { + if in == nil { + return nil + } + out := new(VPCList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VPCList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCNetworkConfiguration) DeepCopyInto(out *VPCNetworkConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCNetworkConfiguration. +func (in *VPCNetworkConfiguration) DeepCopy() *VPCNetworkConfiguration { + if in == nil { + return nil + } + out := new(VPCNetworkConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VPCNetworkConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCNetworkConfigurationList) DeepCopyInto(out *VPCNetworkConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VPCNetworkConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCNetworkConfigurationList. +func (in *VPCNetworkConfigurationList) DeepCopy() *VPCNetworkConfigurationList { + if in == nil { + return nil + } + out := new(VPCNetworkConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VPCNetworkConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCNetworkConfigurationSpec) DeepCopyInto(out *VPCNetworkConfigurationSpec) { + *out = *in + if in.ExternalIPv4Blocks != nil { + in, out := &in.ExternalIPv4Blocks, &out.ExternalIPv4Blocks + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PrivateIPv4CIDRs != nil { + in, out := &in.PrivateIPv4CIDRs, &out.PrivateIPv4CIDRs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCNetworkConfigurationSpec. +func (in *VPCNetworkConfigurationSpec) DeepCopy() *VPCNetworkConfigurationSpec { + if in == nil { + return nil + } + out := new(VPCNetworkConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCNetworkConfigurationStatus) DeepCopyInto(out *VPCNetworkConfigurationStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCNetworkConfigurationStatus. +func (in *VPCNetworkConfigurationStatus) DeepCopy() *VPCNetworkConfigurationStatus { + if in == nil { + return nil + } + out := new(VPCNetworkConfigurationStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCSpec) DeepCopyInto(out *VPCSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCSpec. +func (in *VPCSpec) DeepCopy() *VPCSpec { + if in == nil { + return nil + } + out := new(VPCSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VPCStatus) DeepCopyInto(out *VPCStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PrivateIPv4CIDRs != nil { + in, out := &in.PrivateIPv4CIDRs, &out.PrivateIPv4CIDRs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCStatus. +func (in *VPCStatus) DeepCopy() *VPCStatus { + if in == nil { + return nil + } + out := new(VPCStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha2/doc.go b/pkg/apis/nsx.vmware.com/v1alpha2/doc.go new file mode 100644 index 000000000..440321155 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha2/doc.go @@ -0,0 +1,4 @@ +// +k8s:deepcopy-gen=package +// +groupName=nsx.vmware.com + +package v1alpha2 diff --git a/pkg/apis/nsx.vmware.com/v1alpha2/groupversion_info.go b/pkg/apis/nsx.vmware.com/v1alpha2/groupversion_info.go new file mode 100644 index 000000000..17358a516 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha2/groupversion_info.go @@ -0,0 +1,23 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Package v1alpha2 contains API Schema definitions for the v1alpha2 API group +// +kubebuilder:object:generate=true +// +groupName=nsx.vmware.com +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "nsx.vmware.com", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/pkg/apis/nsx.vmware.com/v1alpha2/ippool_types.go b/pkg/apis/nsx.vmware.com/v1alpha2/ippool_types.go new file mode 100644 index 000000000..469dc43ef --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha2/ippool_types.go @@ -0,0 +1,82 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion + +// IPPool is the Schema for the ippools API. +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`,description="Type of IPPool" +// +kubebuilder:printcolumn:name="Subnets",type=string,JSONPath=`.status.subnets[*].cidr`,description="CIDRs for the Subnet" +type IPPool struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec IPPoolSpec `json:"spec"` + Status IPPoolStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// IPPoolList contains a list of IPPool. +type IPPoolList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IPPool `json:"items"` +} + +// IPPoolSpec defines the desired state of IPPool. +type IPPoolSpec struct { + // Type defines the type of this IPPool, Public or Private. + // +kubebuilder:validation:Enum=Public;Private + // +optional + Type string `json:"type,omitempty"` + // Subnets defines set of subnets need to be allocated. + // +optional + Subnets []SubnetRequest `json:"subnets"` +} + +// IPPoolStatus defines the observed state of IPPool. +type IPPoolStatus struct { + // Subnets defines subnets allocation result. + Subnets []SubnetResult `json:"subnets"` + // Conditions defines current state of the IPPool. + Conditions []v1alpha1.Condition `json:"conditions"` +} + +// SubnetRequest defines the subnet allocation request. +type SubnetRequest struct { + // PrefixLength defines prefix length for this subnet. + PrefixLength int `json:"prefixLength,omitempty"` + + // IPFamily defines the IP family type for this subnet, could be IPv4 or IPv6. + // This is optional, the default is IPv4. + // +kubebuilder:validation:Enum=IPv4;IPv6 + // +kubebuilder:default=IPv4 + IPFamily string `json:"ipFamily,omitempty"` + + // Name defines the name of this subnet. + Name string `json:"name"` +} + +// SubnetResult defines the subnet allocation result. +type SubnetResult struct { + // CIDR defines the allocated CIDR. + CIDR string `json:"cidr"` + + // Name defines the name of this subnet. + Name string `json:"name"` +} + +func init() { + SchemeBuilder.Register(&IPPool{}, &IPPoolList{}) +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha2/register.go b/pkg/apis/nsx.vmware.com/v1alpha2/register.go new file mode 100644 index 000000000..26d0e5656 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha2/register.go @@ -0,0 +1,12 @@ +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = GroupVersion + +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/apis/nsx.vmware.com/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/nsx.vmware.com/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 000000000..7dadeb757 --- /dev/null +++ b/pkg/apis/nsx.vmware.com/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPool) DeepCopyInto(out *IPPool) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPool. +func (in *IPPool) DeepCopy() *IPPool { + if in == nil { + return nil + } + out := new(IPPool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPPool) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolList) DeepCopyInto(out *IPPoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IPPool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolList. +func (in *IPPoolList) DeepCopy() *IPPoolList { + if in == nil { + return nil + } + out := new(IPPoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPPoolList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolSpec) DeepCopyInto(out *IPPoolSpec) { + *out = *in + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetRequest, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolSpec. +func (in *IPPoolSpec) DeepCopy() *IPPoolSpec { + if in == nil { + return nil + } + out := new(IPPoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolStatus) DeepCopyInto(out *IPPoolStatus) { + *out = *in + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetResult, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1alpha1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolStatus. +func (in *IPPoolStatus) DeepCopy() *IPPoolStatus { + if in == nil { + return nil + } + out := new(IPPoolStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetRequest) DeepCopyInto(out *SubnetRequest) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetRequest. +func (in *SubnetRequest) DeepCopy() *SubnetRequest { + if in == nil { + return nil + } + out := new(SubnetRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetResult) DeepCopyInto(out *SubnetResult) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetResult. +func (in *SubnetResult) DeepCopy() *SubnetResult { + if in == nil { + return nil + } + out := new(SubnetResult) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/v1alpha1/doc.go b/pkg/apis/v1alpha1/doc.go new file mode 100644 index 000000000..73e7087ed --- /dev/null +++ b/pkg/apis/v1alpha1/doc.go @@ -0,0 +1,4 @@ +// +k8s:deepcopy-gen=package +// +groupName=nsx.vmware.com + +package v1alpha1 diff --git a/pkg/apis/v1alpha1/ippool_types.go b/pkg/apis/v1alpha1/ippool_types.go new file mode 100644 index 000000000..6620529b0 --- /dev/null +++ b/pkg/apis/v1alpha1/ippool_types.go @@ -0,0 +1,73 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// IPPool is the Schema for the ippools API. +type IPPool struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec IPPoolSpec `json:"spec"` + Status IPPoolStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// IPPoolList contains a list of IPPool. +type IPPoolList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IPPool `json:"items"` +} + +// IPPoolSpec defines the desired state of IPPool. +type IPPoolSpec struct { + // Subnets defines set of subnets need to be allocated. + // +optional + Subnets []SubnetRequest `json:"subnets"` +} + +// IPPoolStatus defines the observed state of IPPool. +type IPPoolStatus struct { + // Subnets defines subnets allocation result. + Subnets []SubnetResult `json:"subnets"` + // Conditions defines current state of the IPPool. + Conditions []Condition `json:"conditions"` +} + +// SubnetRequest defines the subnet allocation request. +type SubnetRequest struct { + // PrefixLength defines prefix length for this subnet. + PrefixLength int `json:"prefixLength,omitempty"` + + // IPFamily defines the IP family type for this subnet, could be IPv4 or IPv6. + // This is optional, the default is IPv4. + // +kubebuilder:validation:Enum=IPv4;IPv6 + // +kubebuilder:default=IPv4 + IPFamily string `json:"ipFamily,omitempty"` + + // Name defines the name of this subnet. + Name string `json:"name"` +} + +// SubnetResult defines the subnet allocation result. +type SubnetResult struct { + // CIDR defines the allocated CIDR. + CIDR string `json:"cidr"` + + // Name defines the name of this subnet. + Name string `json:"name"` +} + +func init() { + SchemeBuilder.Register(&IPPool{}, &IPPoolList{}) +} diff --git a/pkg/apis/v1alpha1/nsxserviceaccount_types.go b/pkg/apis/v1alpha1/nsxserviceaccount_types.go index a788948d8..3a32bd809 100644 --- a/pkg/apis/v1alpha1/nsxserviceaccount_types.go +++ b/pkg/apis/v1alpha1/nsxserviceaccount_types.go @@ -73,8 +73,10 @@ type NSXServiceAccountStatus struct { Secrets []NSXSecret `json:"secrets,omitempty"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // NSXServiceAccount is the Schema for the nsxserviceaccounts API type NSXServiceAccount struct { diff --git a/pkg/apis/v1alpha1/register.go b/pkg/apis/v1alpha1/register.go new file mode 100644 index 000000000..2dfd631eb --- /dev/null +++ b/pkg/apis/v1alpha1/register.go @@ -0,0 +1,12 @@ +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = GroupVersion + +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/apis/v1alpha1/securitypolicy_types.go b/pkg/apis/v1alpha1/securitypolicy_types.go index 66532d22d..246965a80 100644 --- a/pkg/apis/v1alpha1/securitypolicy_types.go +++ b/pkg/apis/v1alpha1/securitypolicy_types.go @@ -113,8 +113,10 @@ type SecurityPolicyStatus struct { Conditions []Condition `json:"conditions"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // SecurityPolicy is the Schema for the securitypolicies API. type SecurityPolicy struct { diff --git a/pkg/apis/v1alpha1/staticroute_types.go b/pkg/apis/v1alpha1/staticroute_types.go index d1de4081c..87d6b7b80 100644 --- a/pkg/apis/v1alpha1/staticroute_types.go +++ b/pkg/apis/v1alpha1/staticroute_types.go @@ -1,4 +1,4 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package v1alpha1 @@ -31,14 +31,18 @@ type NextHop struct { // StaticRouteStatus defines the observed state of StaticRoute. type StaticRouteStatus struct { - Conditions []StaticRouteCondition `json:"conditions"` + Conditions []StaticRouteCondition `json:"conditions"` + NSXResourcePath string `json:"nsxResourcePath"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:storageversion // StaticRoute is the Schema for the staticroutes API. +// +kubebuilder:printcolumn:name="Network",type=string,JSONPath=`.spec.network`,description="Network in CIDR format" +// +kubebuilder:printcolumn:name="NextHops",type=string,JSONPath=`.spec.nextHops[*].ipAddress`,description="Next Hops" type StaticRoute struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/v1alpha1/subnet_types.go b/pkg/apis/v1alpha1/subnet_types.go index 3c4d6d891..5473a8021 100644 --- a/pkg/apis/v1alpha1/subnet_types.go +++ b/pkg/apis/v1alpha1/subnet_types.go @@ -1,4 +1,4 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package v1alpha1 @@ -12,15 +12,11 @@ type AccessMode string // SubnetSpec defines the desired state of Subnet. type SubnetSpec struct { // Size of Subnet based upon estimated workload count. - // Defaults to 64. - // +kubebuilder:default:=64 // +kubebuilder:validation:Maximum:=65536 // +kubebuilder:validation:Minimum:=16 IPv4SubnetSize int `json:"ipv4SubnetSize,omitempty"` // Access mode of Subnet, accessible only from within VPC or from outside VPC. - // Defaults to private. - // +kubebuilder:default:=private - // +kubebuilder:validation:Enum=private;public + // +kubebuilder:validation:Enum=Private;Public AccessMode AccessMode `json:"accessMode,omitempty"` // Subnet CIDRS. // +kubebuilder:validation:MinItems=0 @@ -34,15 +30,20 @@ type SubnetSpec struct { // SubnetStatus defines the observed state of Subnet. type SubnetStatus struct { - NSXResourcePath string `json:"nsxResourcePath"` - IPAddresses []string `json:"ipAddresses"` - Conditions []Condition `json:"conditions"` + NSXResourcePath string `json:"nsxResourcePath,omitempty"` + IPAddresses []string `json:"ipAddresses,omitempty"` + Conditions []Condition `json:"conditions,omitempty"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // Subnet is the Schema for the subnets API. +// +kubebuilder:printcolumn:name="AccessMode",type=string,JSONPath=`.spec.accessMode`,description="Access mode of Subnet" +// +kubebuilder:printcolumn:name="IPv4SubnetSize",type=string,JSONPath=`.spec.ipv4SubnetSize`,description="Size of Subnet" +// +kubebuilder:printcolumn:name="IPAddresses",type=string,JSONPath=`.status.ipAddresses[*]`,description="CIDRs for the Subnet" type Subnet struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/v1alpha1/subnetport_types.go b/pkg/apis/v1alpha1/subnetport_types.go index 877a1a681..d395692f7 100644 --- a/pkg/apis/v1alpha1/subnetport_types.go +++ b/pkg/apis/v1alpha1/subnetport_types.go @@ -1,4 +1,4 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package v1alpha1 @@ -38,10 +38,15 @@ type SubnetPortIPAddress struct { Netmask string `json:"netmask,omitempty"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // SubnetPort is the Schema for the subnetports API. +// +kubebuilder:printcolumn:name="VIFID",type=string,JSONPath=`.status.vifID`,description="Attachment VIF ID owned by the SubnetPort" +// +kubebuilder:printcolumn:name="IPAddress",type=string,JSONPath=`.status.ipAddresses[0].ip`,description="IP Address of the SubnetPort" +// +kubebuilder:printcolumn:name="MACAddress",type=string,JSONPath=`.status.macAddress`,description="MAC Address of the SubnetPort" type SubnetPort struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/v1alpha1/subnetset_types.go b/pkg/apis/v1alpha1/subnetset_types.go index 0be31c259..5c6864893 100644 --- a/pkg/apis/v1alpha1/subnetset_types.go +++ b/pkg/apis/v1alpha1/subnetset_types.go @@ -1,4 +1,4 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package v1alpha1 @@ -10,15 +10,11 @@ import ( // SubnetSetSpec defines the desired state of SubnetSet. type SubnetSetSpec struct { // Size of Subnet based upon estimated workload count. - // Defaults to 64. - // +kubebuilder:default:=64 // +kubebuilder:validation:Maximum:=65536 // +kubebuilder:validation:Minimum:=16 IPv4SubnetSize int `json:"ipv4SubnetSize,omitempty"` // Access mode of Subnet, accessible only from within VPC or from outside VPC. - // Defaults to private. - // +kubebuilder:default:=private - // +kubebuilder:validation:Enum=private;public + // +kubebuilder:validation:Enum=Private;Public AccessMode AccessMode `json:"accessMode,omitempty"` // Subnet advanced configuration. AdvancedConfig AdvancedConfig `json:"advancedConfig,omitempty"` @@ -34,14 +30,19 @@ type SubnetInfo struct { // SubnetSetStatus defines the observed state of SubnetSet. type SubnetSetStatus struct { - Conditions []Condition `json:"conditions"` - Subnets []SubnetInfo `json:"subnets"` + Conditions []Condition `json:"conditions,omitempty"` + Subnets []SubnetInfo `json:"subnets,omitempty"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // SubnetSet is the Schema for the subnetsets API. +// +kubebuilder:printcolumn:name="AccessMode",type=string,JSONPath=`.spec.accessMode`,description="Access mode of Subnet" +// +kubebuilder:printcolumn:name="IPv4SubnetSize",type=string,JSONPath=`.spec.ipv4SubnetSize`,description="Size of Subnet" +// +kubebuilder:printcolumn:name="IPAddresses",type=string,JSONPath=`.status.subnets[*].ipAddresses[*]`,description="CIDRs for the Subnet" type SubnetSet struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/v1alpha1/vpc_types.go b/pkg/apis/v1alpha1/vpc_types.go index ffb6debbc..a552df36e 100644 --- a/pkg/apis/v1alpha1/vpc_types.go +++ b/pkg/apis/v1alpha1/vpc_types.go @@ -1,4 +1,4 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package v1alpha1 @@ -7,11 +7,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:storageversion // VPC is the Schema for the VPC API +// +kubebuilder:printcolumn:name="PrivateIPv4CIDRs",type=string,JSONPath=`.status.privateIPv4CIDRs`,description="Private IPv4 CIDRs" +// +kubebuilder:printcolumn:name="SNATIP",type=string,JSONPath=`.status.defaultSNATIP`,description="Default SNAT IP for Private Subnets" +// +kubebuilder:printcolumn:name="LBSubnetCIDR",type=string,JSONPath=`.status.lbSubnetCIDR`,description="CIDR for the load balancer Subnet" type VPC struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -38,12 +42,14 @@ type VPCStatus struct { Conditions []Condition `json:"conditions"` // NSX VPC Policy API resource path. NSXResourcePath string `json:"nsxResourcePath"` - // Default SNAT IP for private Subnets. + // Default SNAT IP for Private Subnets. DefaultSNATIP string `json:"defaultSNATIP"` // NSX PolicyPath for the load balancer Subnet. LBSubnetPath string `json:"lbSubnetPath"` // CIDR for the load balancer Subnet. LBSubnetCIDR string `json:"lbSubnetCIDR"` + // Private CIDRs used for the VPC. + PrivateIPv4CIDRs []string `json:"privateIPv4CIDRs"` } func init() { diff --git a/pkg/apis/v1alpha1/vpcnetworkconfiguration_types.go b/pkg/apis/v1alpha1/vpcnetworkconfiguration_types.go index bba8854d7..2571b800f 100644 --- a/pkg/apis/v1alpha1/vpcnetworkconfiguration_types.go +++ b/pkg/apis/v1alpha1/vpcnetworkconfiguration_types.go @@ -1,4 +1,4 @@ -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2022-2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ // +kubebuilder:object:generate=true @@ -9,17 +9,10 @@ import ( ) const ( - AccessModePublic string = "public" - AccessModePrivate string = "private" + AccessModePublic string = "Public" + AccessModePrivate string = "Private" ) -// Load balancer endpoint configuration. -type LoadBalancerVPCEndpoint struct { - // Flag to enable load balancer for vpc. - // +kubebuilder:default=false - Enabled bool `json:"enabled,omitempty"` -} - // VPCNetworkConfigurationSpec defines the desired state of VPCNetworkConfiguration. // There is a default VPCNetworkConfiguration that applies to Namespaces // do not have a VPCNetworkConfiguration assigned. When a field is not set @@ -32,13 +25,11 @@ type VPCNetworkConfigurationSpec struct { EdgeClusterPath string `json:"edgeClusterPath,omitempty"` // NSX-T Project the Namespace associated with. NSXTProject string `json:"nsxtProject,omitempty"` - // Load balancer endpoint configuration. - LoadBalancerVPCEndpoint LoadBalancerVPCEndpoint `json:"loadBalancerVPCEndpoint,omitempty"` // NSX-T IPv4 Block paths used to allocate external Subnets. // +kubebuilder:validation:MinItems=0 // +kubebuilder:validation:MaxItems=5 ExternalIPv4Blocks []string `json:"externalIPv4Blocks,omitempty"` - // Private IPv4 CIDRs used to allocate private Subnets. + // Private IPv4 CIDRs used to allocate Private Subnets. // +kubebuilder:validation:MinItems=0 // +kubebuilder:validation:MaxItems=5 PrivateIPv4CIDRs []string `json:"privateIPv4CIDRs,omitempty"` @@ -47,8 +38,8 @@ type VPCNetworkConfigurationSpec struct { // +kubebuilder:default=26 DefaultIPv4SubnetSize int `json:"defaultIPv4SubnetSize,omitempty"` // DefaultSubnetAccessMode defines the access mode of the default SubnetSet for PodVM and VM. - // Must be public or private. - // +kubebuilder:validation:Enum=public;private + // Must be Public or Private. + // +kubebuilder:validation:Enum=Public;Private DefaultSubnetAccessMode string `json:"defaultSubnetAccessMode,omitempty"` } @@ -58,14 +49,16 @@ type VPCNetworkConfigurationStatus struct { Conditions []Condition `json:"conditions"` } +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // VPCNetworkConfiguration is the Schema for the vpcnetworkconfigurations API. // +kubebuilder:resource:scope="Cluster" -// +kubebuilder:printcolumn:name="NSXTProject",type=string,JSONPath=`.spec.NSXTProject`,description="NSXTProject the Namespace associated with" -// +kubebuilder:printcolumn:name="PublicIPv4Blocks",type=string,JSONPath=`.spec.ExternalIPv4Blocks`,description="ExternalIPv4Blocks assigned to the Namespace" -// +kubebuilder:printcolumn:name="PrivateIPv4CIDRs",type=string,JSONPath=`.spec.PrivateIPv4CIDRs`,description="PrivateIPv4CIDRs assigned to the Namespace" +// +kubebuilder:printcolumn:name="NSXTProject",type=string,JSONPath=`.spec.nsxtProject`,description="NSXTProject the Namespace associated with" +// +kubebuilder:printcolumn:name="ExternalIPv4Blocks",type=string,JSONPath=`.spec.externalIPv4Blocks`,description="ExternalIPv4Blocks assigned to the Namespace" +// +kubebuilder:printcolumn:name="PrivateIPv4CIDRs",type=string,JSONPath=`.spec.privateIPv4CIDRs`,description="PrivateIPv4CIDRs assigned to the Namespace" type VPCNetworkConfiguration struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go index d4d05d20a..2d5e262e6 100644 --- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ // Code generated by controller-gen. DO NOT EDIT. @@ -97,16 +97,107 @@ func (in *IPBlock) DeepCopy() *IPBlock { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LoadBalancerVPCEndpoint) DeepCopyInto(out *LoadBalancerVPCEndpoint) { +func (in *IPPool) DeepCopyInto(out *IPPool) { *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancerVPCEndpoint. -func (in *LoadBalancerVPCEndpoint) DeepCopy() *LoadBalancerVPCEndpoint { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPool. +func (in *IPPool) DeepCopy() *IPPool { if in == nil { return nil } - out := new(LoadBalancerVPCEndpoint) + out := new(IPPool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPPool) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolList) DeepCopyInto(out *IPPoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IPPool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolList. +func (in *IPPoolList) DeepCopy() *IPPoolList { + if in == nil { + return nil + } + out := new(IPPoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPPoolList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolSpec) DeepCopyInto(out *IPPoolSpec) { + *out = *in + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetRequest, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolSpec. +func (in *IPPoolSpec) DeepCopy() *IPPoolSpec { + if in == nil { + return nil + } + out := new(IPPoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolStatus) DeepCopyInto(out *IPPoolStatus) { + *out = *in + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetResult, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolStatus. +func (in *IPPoolStatus) DeepCopy() *IPPoolStatus { + if in == nil { + return nil + } + out := new(IPPoolStatus) in.DeepCopyInto(out) return out } @@ -868,6 +959,36 @@ func (in *SubnetPortStatus) DeepCopy() *SubnetPortStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetRequest) DeepCopyInto(out *SubnetRequest) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetRequest. +func (in *SubnetRequest) DeepCopy() *SubnetRequest { + if in == nil { + return nil + } + out := new(SubnetRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetResult) DeepCopyInto(out *SubnetResult) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetResult. +func (in *SubnetResult) DeepCopy() *SubnetResult { + if in == nil { + return nil + } + out := new(SubnetResult) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubnetSet) DeepCopyInto(out *SubnetSet) { *out = *in @@ -1143,7 +1264,6 @@ func (in *VPCNetworkConfigurationList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VPCNetworkConfigurationSpec) DeepCopyInto(out *VPCNetworkConfigurationSpec) { *out = *in - out.LoadBalancerVPCEndpoint = in.LoadBalancerVPCEndpoint if in.ExternalIPv4Blocks != nil { in, out := &in.ExternalIPv4Blocks, &out.ExternalIPv4Blocks *out = make([]string, len(*in)) @@ -1213,6 +1333,11 @@ func (in *VPCStatus) DeepCopyInto(out *VPCStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.PrivateIPv4CIDRs != nil { + in, out := &in.PrivateIPv4CIDRs, &out.PrivateIPv4CIDRs + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VPCStatus. diff --git a/pkg/apis/v1alpha2/doc.go b/pkg/apis/v1alpha2/doc.go new file mode 100644 index 000000000..440321155 --- /dev/null +++ b/pkg/apis/v1alpha2/doc.go @@ -0,0 +1,4 @@ +// +k8s:deepcopy-gen=package +// +groupName=nsx.vmware.com + +package v1alpha2 diff --git a/pkg/apis/v1alpha2/ippool_types.go b/pkg/apis/v1alpha2/ippool_types.go index b37325994..469dc43ef 100644 --- a/pkg/apis/v1alpha2/ippool_types.go +++ b/pkg/apis/v1alpha2/ippool_types.go @@ -9,10 +9,14 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" ) +// +genclient //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:storageversion // IPPool is the Schema for the ippools API. +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`,description="Type of IPPool" +// +kubebuilder:printcolumn:name="Subnets",type=string,JSONPath=`.status.subnets[*].cidr`,description="CIDRs for the Subnet" type IPPool struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` @@ -32,10 +36,10 @@ type IPPoolList struct { // IPPoolSpec defines the desired state of IPPool. type IPPoolSpec struct { - // Type defines the type of this IPPool, public or private. - // +kubebuilder:validation:Enum=public;private - // +kubebuilder:default=private - Type string `json:"type"` + // Type defines the type of this IPPool, Public or Private. + // +kubebuilder:validation:Enum=Public;Private + // +optional + Type string `json:"type,omitempty"` // Subnets defines set of subnets need to be allocated. // +optional Subnets []SubnetRequest `json:"subnets"` diff --git a/pkg/apis/v1alpha2/register.go b/pkg/apis/v1alpha2/register.go new file mode 100644 index 000000000..26d0e5656 --- /dev/null +++ b/pkg/apis/v1alpha2/register.go @@ -0,0 +1,12 @@ +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = GroupVersion + +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/pkg/apis/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/v1alpha2/zz_generated.deepcopy.go index 7b99fda69..7dadeb757 100644 --- a/pkg/apis/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha2/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -/* Copyright © 2022 VMware, Inc. All Rights Reserved. +/* Copyright © 2023 VMware, Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ // Code generated by controller-gen. DO NOT EDIT. diff --git a/pkg/clean/clean.go b/pkg/clean/clean.go new file mode 100644 index 000000000..a2b3b4409 --- /dev/null +++ b/pkg/clean/clean.go @@ -0,0 +1,111 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package clean + +import ( + "fmt" + + "github.com/vmware-tanzu/nsx-operator/pkg/config" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/ippool" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/securitypolicy" + sr "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/staticroute" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetport" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +var log = logger.Log + +// Clean cleans up NSX resources, +// including security policy, static route, subnet, subnet port, subnet set, vpc, ip pool, nsx service account +// it is usually used when nsx-operator is uninstalled and remove all the resources created by nsx-operator +// return error if any, return nil if no error +func Clean(cf *config.NSXOperatorConfig) error { + log.Info("starting NSX cleanup") + if err := cf.ValidateConfigFromCmd(); err != nil { + return fmt.Errorf("failed to validate config: %w", err) + } + if cleanupService, err := InitializeCleanupService(cf); err != nil { + return fmt.Errorf("failed to initialize cleanup service: %w", err) + } else if cleanupService.err != nil { + return fmt.Errorf("failed to initialize cleanup service: %w", cleanupService.err) + } else { + for _, clean := range cleanupService.cleans { + if err := clean.Cleanup(); err != nil { + return fmt.Errorf("failed to clean up: %w", err) + } + } + } + log.Info("cleanup NSX resources successfully") + return nil +} + +// InitializeCleanupService initializes all the CR services +func InitializeCleanupService(cf *config.NSXOperatorConfig) (*CleanupService, error) { + cleanupService := NewCleanupService() + + nsxClient := nsx.GetClient(cf) + if nsxClient == nil { + return cleanupService, fmt.Errorf("failed to get nsx client") + } + + var commonService = common.Service{ + NSXClient: nsxClient, + NSXConfig: cf, + } + + vpcService, vpcErr := vpc.InitializeVPC(commonService) + commonctl.ServiceMediator.VPCService = vpcService + + // initialize all the CR services + // Use Fluent Interface to escape error check hell + + wrapInitializeSubnetService := func(service common.Service) cleanupFunc { + return func() (cleanup, error) { + return subnet.InitializeSubnetService(service) + } + } + wrapInitializeSecurityPolicy := func(service common.Service) cleanupFunc { + return func() (cleanup, error) { + return securitypolicy.InitializeSecurityPolicy(service) + } + } + wrapInitializeIPPool := func(service common.Service) cleanupFunc { + return func() (cleanup, error) { + return ippool.InitializeIPPool(service) + } + } + + wrapInitializeVPC := func(service common.Service) cleanupFunc { + return func() (cleanup, error) { + return vpcService, vpcErr + } + } + + wrapInitializeStaticRoute := func(service common.Service) cleanupFunc { + return func() (cleanup, error) { + return sr.InitializeStaticRoute(service) + } + } + + wrapInitializeSubnetPort := func(service common.Service) cleanupFunc { + return func() (cleanup, error) { + return subnetport.InitializeSubnetPort(service) + } + } + // TODO: initialize other CR services + cleanupService = cleanupService. + AddCleanupService(wrapInitializeSubnetPort(commonService)). + AddCleanupService(wrapInitializeSubnetService(commonService)). + AddCleanupService(wrapInitializeSecurityPolicy(commonService)). + AddCleanupService(wrapInitializeIPPool(commonService)). + AddCleanupService(wrapInitializeStaticRoute(commonService)). + AddCleanupService(wrapInitializeVPC(commonService)) + + return cleanupService, nil +} diff --git a/pkg/clean/types.go b/pkg/clean/types.go new file mode 100644 index 000000000..c71207bc1 --- /dev/null +++ b/pkg/clean/types.go @@ -0,0 +1,31 @@ +package clean + +type cleanup interface { + Cleanup() error +} + +type cleanupFunc func() (cleanup, error) + +type CleanupService struct { + cleans []cleanup + err error +} + +func NewCleanupService() *CleanupService { + return &CleanupService{} +} + +func (c *CleanupService) AddCleanupService(f cleanupFunc) *CleanupService { + var clean cleanup + if c.err != nil { + return c + } + + clean, c.err = f() + if c.err != nil { + return c + } + + c.cleans = append(c.cleans, clean) + return c +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go new file mode 100644 index 000000000..acd4d8b9d --- /dev/null +++ b/pkg/client/clientset/versioned/clientset.go @@ -0,0 +1,120 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package versioned + +import ( + "fmt" + "net/http" + + nsxv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1" + nsxv1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2" + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + NsxV1alpha1() nsxv1alpha1.NsxV1alpha1Interface + NsxV1alpha2() nsxv1alpha2.NsxV1alpha2Interface +} + +// Clientset contains the clients for groups. +type Clientset struct { + *discovery.DiscoveryClient + nsxV1alpha1 *nsxv1alpha1.NsxV1alpha1Client + nsxV1alpha2 *nsxv1alpha2.NsxV1alpha2Client +} + +// NsxV1alpha1 retrieves the NsxV1alpha1Client +func (c *Clientset) NsxV1alpha1() nsxv1alpha1.NsxV1alpha1Interface { + return c.nsxV1alpha1 +} + +// NsxV1alpha2 retrieves the NsxV1alpha2Client +func (c *Clientset) NsxV1alpha2() nsxv1alpha2.NsxV1alpha2Interface { + return c.nsxV1alpha2 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error + cs.nsxV1alpha1, err = nsxv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + cs.nsxV1alpha2, err = nsxv1alpha2.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.nsxV1alpha1 = nsxv1alpha1.New(c) + cs.nsxV1alpha2 = nsxv1alpha2.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 000000000..73d4d324a --- /dev/null +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,79 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + clientset "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + nsxv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1" + fakensxv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake" + nsxv1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2" + fakensxv1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// NsxV1alpha1 retrieves the NsxV1alpha1Client +func (c *Clientset) NsxV1alpha1() nsxv1alpha1.NsxV1alpha1Interface { + return &fakensxv1alpha1.FakeNsxV1alpha1{Fake: &c.Fake} +} + +// NsxV1alpha2 retrieves the NsxV1alpha2Client +func (c *Clientset) NsxV1alpha2() nsxv1alpha2.NsxV1alpha2Interface { + return &fakensxv1alpha2.FakeNsxV1alpha2{Fake: &c.Fake} +} diff --git a/pkg/client/clientset/versioned/fake/doc.go b/pkg/client/clientset/versioned/fake/doc.go new file mode 100644 index 000000000..497823177 --- /dev/null +++ b/pkg/client/clientset/versioned/fake/doc.go @@ -0,0 +1,7 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go new file mode 100644 index 000000000..8b9c91210 --- /dev/null +++ b/pkg/client/clientset/versioned/fake/register.go @@ -0,0 +1,45 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + nsxv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + nsxv1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + nsxv1alpha1.AddToScheme, + nsxv1alpha2.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/pkg/client/clientset/versioned/scheme/doc.go b/pkg/client/clientset/versioned/scheme/doc.go new file mode 100644 index 000000000..b4cbd389f --- /dev/null +++ b/pkg/client/clientset/versioned/scheme/doc.go @@ -0,0 +1,7 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go new file mode 100644 index 000000000..52b9ff6c9 --- /dev/null +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -0,0 +1,45 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package scheme + +import ( + nsxv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + nsxv1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + nsxv1alpha1.AddToScheme, + nsxv1alpha2.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/doc.go new file mode 100644 index 000000000..7e1618024 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/doc.go @@ -0,0 +1,7 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/doc.go new file mode 100644 index 000000000..f7c0364c0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/doc.go @@ -0,0 +1,7 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_ippool.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_ippool.go new file mode 100644 index 000000000..578c05b40 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_ippool.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeIPPools implements IPPoolInterface +type FakeIPPools struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var ippoolsResource = v1alpha1.SchemeGroupVersion.WithResource("ippools") + +var ippoolsKind = v1alpha1.SchemeGroupVersion.WithKind("IPPool") + +// Get takes name of the iPPool, and returns the corresponding iPPool object, and an error if there is any. +func (c *FakeIPPools) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(ippoolsResource, c.ns, name), &v1alpha1.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IPPool), err +} + +// List takes label and field selectors, and returns the list of IPPools that match those selectors. +func (c *FakeIPPools) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IPPoolList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(ippoolsResource, ippoolsKind, c.ns, opts), &v1alpha1.IPPoolList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.IPPoolList{ListMeta: obj.(*v1alpha1.IPPoolList).ListMeta} + for _, item := range obj.(*v1alpha1.IPPoolList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested iPPools. +func (c *FakeIPPools) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(ippoolsResource, c.ns, opts)) + +} + +// Create takes the representation of a iPPool and creates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *FakeIPPools) Create(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.CreateOptions) (result *v1alpha1.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(ippoolsResource, c.ns, iPPool), &v1alpha1.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IPPool), err +} + +// Update takes the representation of a iPPool and updates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *FakeIPPools) Update(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.UpdateOptions) (result *v1alpha1.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(ippoolsResource, c.ns, iPPool), &v1alpha1.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IPPool), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeIPPools) UpdateStatus(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.UpdateOptions) (*v1alpha1.IPPool, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(ippoolsResource, "status", c.ns, iPPool), &v1alpha1.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IPPool), err +} + +// Delete takes name of the iPPool and deletes it. Returns an error if one occurs. +func (c *FakeIPPools) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(ippoolsResource, c.ns, name, opts), &v1alpha1.IPPool{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeIPPools) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(ippoolsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.IPPoolList{}) + return err +} + +// Patch applies the patch and returns the patched iPPool. +func (c *FakeIPPools) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(ippoolsResource, c.ns, name, pt, data, subresources...), &v1alpha1.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IPPool), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_nsx.vmware.com_client.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_nsx.vmware.com_client.go new file mode 100644 index 000000000..45ddac990 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_nsx.vmware.com_client.go @@ -0,0 +1,59 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeNsxV1alpha1 struct { + *testing.Fake +} + +func (c *FakeNsxV1alpha1) IPPools(namespace string) v1alpha1.IPPoolInterface { + return &FakeIPPools{c, namespace} +} + +func (c *FakeNsxV1alpha1) NSXServiceAccounts(namespace string) v1alpha1.NSXServiceAccountInterface { + return &FakeNSXServiceAccounts{c, namespace} +} + +func (c *FakeNsxV1alpha1) SecurityPolicies(namespace string) v1alpha1.SecurityPolicyInterface { + return &FakeSecurityPolicies{c, namespace} +} + +func (c *FakeNsxV1alpha1) StaticRoutes(namespace string) v1alpha1.StaticRouteInterface { + return &FakeStaticRoutes{c, namespace} +} + +func (c *FakeNsxV1alpha1) Subnets(namespace string) v1alpha1.SubnetInterface { + return &FakeSubnets{c, namespace} +} + +func (c *FakeNsxV1alpha1) SubnetPorts(namespace string) v1alpha1.SubnetPortInterface { + return &FakeSubnetPorts{c, namespace} +} + +func (c *FakeNsxV1alpha1) SubnetSets(namespace string) v1alpha1.SubnetSetInterface { + return &FakeSubnetSets{c, namespace} +} + +func (c *FakeNsxV1alpha1) VPCs(namespace string) v1alpha1.VPCInterface { + return &FakeVPCs{c, namespace} +} + +func (c *FakeNsxV1alpha1) VPCNetworkConfigurations(namespace string) v1alpha1.VPCNetworkConfigurationInterface { + return &FakeVPCNetworkConfigurations{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeNsxV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_nsxserviceaccount.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_nsxserviceaccount.go new file mode 100644 index 000000000..ec092ee4d --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_nsxserviceaccount.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeNSXServiceAccounts implements NSXServiceAccountInterface +type FakeNSXServiceAccounts struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var nsxserviceaccountsResource = v1alpha1.SchemeGroupVersion.WithResource("nsxserviceaccounts") + +var nsxserviceaccountsKind = v1alpha1.SchemeGroupVersion.WithKind("NSXServiceAccount") + +// Get takes name of the nSXServiceAccount, and returns the corresponding nSXServiceAccount object, and an error if there is any. +func (c *FakeNSXServiceAccounts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.NSXServiceAccount, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(nsxserviceaccountsResource, c.ns, name), &v1alpha1.NSXServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.NSXServiceAccount), err +} + +// List takes label and field selectors, and returns the list of NSXServiceAccounts that match those selectors. +func (c *FakeNSXServiceAccounts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NSXServiceAccountList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(nsxserviceaccountsResource, nsxserviceaccountsKind, c.ns, opts), &v1alpha1.NSXServiceAccountList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.NSXServiceAccountList{ListMeta: obj.(*v1alpha1.NSXServiceAccountList).ListMeta} + for _, item := range obj.(*v1alpha1.NSXServiceAccountList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested nSXServiceAccounts. +func (c *FakeNSXServiceAccounts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(nsxserviceaccountsResource, c.ns, opts)) + +} + +// Create takes the representation of a nSXServiceAccount and creates it. Returns the server's representation of the nSXServiceAccount, and an error, if there is any. +func (c *FakeNSXServiceAccounts) Create(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.CreateOptions) (result *v1alpha1.NSXServiceAccount, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(nsxserviceaccountsResource, c.ns, nSXServiceAccount), &v1alpha1.NSXServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.NSXServiceAccount), err +} + +// Update takes the representation of a nSXServiceAccount and updates it. Returns the server's representation of the nSXServiceAccount, and an error, if there is any. +func (c *FakeNSXServiceAccounts) Update(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.UpdateOptions) (result *v1alpha1.NSXServiceAccount, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(nsxserviceaccountsResource, c.ns, nSXServiceAccount), &v1alpha1.NSXServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.NSXServiceAccount), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeNSXServiceAccounts) UpdateStatus(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.UpdateOptions) (*v1alpha1.NSXServiceAccount, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(nsxserviceaccountsResource, "status", c.ns, nSXServiceAccount), &v1alpha1.NSXServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.NSXServiceAccount), err +} + +// Delete takes name of the nSXServiceAccount and deletes it. Returns an error if one occurs. +func (c *FakeNSXServiceAccounts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(nsxserviceaccountsResource, c.ns, name, opts), &v1alpha1.NSXServiceAccount{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeNSXServiceAccounts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(nsxserviceaccountsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.NSXServiceAccountList{}) + return err +} + +// Patch applies the patch and returns the patched nSXServiceAccount. +func (c *FakeNSXServiceAccounts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NSXServiceAccount, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(nsxserviceaccountsResource, c.ns, name, pt, data, subresources...), &v1alpha1.NSXServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.NSXServiceAccount), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_securitypolicy.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_securitypolicy.go new file mode 100644 index 000000000..6f17b5cec --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_securitypolicy.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeSecurityPolicies implements SecurityPolicyInterface +type FakeSecurityPolicies struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var securitypoliciesResource = v1alpha1.SchemeGroupVersion.WithResource("securitypolicies") + +var securitypoliciesKind = v1alpha1.SchemeGroupVersion.WithKind("SecurityPolicy") + +// Get takes name of the securityPolicy, and returns the corresponding securityPolicy object, and an error if there is any. +func (c *FakeSecurityPolicies) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(securitypoliciesResource, c.ns, name), &v1alpha1.SecurityPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SecurityPolicy), err +} + +// List takes label and field selectors, and returns the list of SecurityPolicies that match those selectors. +func (c *FakeSecurityPolicies) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SecurityPolicyList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(securitypoliciesResource, securitypoliciesKind, c.ns, opts), &v1alpha1.SecurityPolicyList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.SecurityPolicyList{ListMeta: obj.(*v1alpha1.SecurityPolicyList).ListMeta} + for _, item := range obj.(*v1alpha1.SecurityPolicyList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested securityPolicies. +func (c *FakeSecurityPolicies) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(securitypoliciesResource, c.ns, opts)) + +} + +// Create takes the representation of a securityPolicy and creates it. Returns the server's representation of the securityPolicy, and an error, if there is any. +func (c *FakeSecurityPolicies) Create(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.CreateOptions) (result *v1alpha1.SecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(securitypoliciesResource, c.ns, securityPolicy), &v1alpha1.SecurityPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SecurityPolicy), err +} + +// Update takes the representation of a securityPolicy and updates it. Returns the server's representation of the securityPolicy, and an error, if there is any. +func (c *FakeSecurityPolicies) Update(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.UpdateOptions) (result *v1alpha1.SecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(securitypoliciesResource, c.ns, securityPolicy), &v1alpha1.SecurityPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SecurityPolicy), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeSecurityPolicies) UpdateStatus(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.UpdateOptions) (*v1alpha1.SecurityPolicy, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(securitypoliciesResource, "status", c.ns, securityPolicy), &v1alpha1.SecurityPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SecurityPolicy), err +} + +// Delete takes name of the securityPolicy and deletes it. Returns an error if one occurs. +func (c *FakeSecurityPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(securitypoliciesResource, c.ns, name, opts), &v1alpha1.SecurityPolicy{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSecurityPolicies) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(securitypoliciesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.SecurityPolicyList{}) + return err +} + +// Patch applies the patch and returns the patched securityPolicy. +func (c *FakeSecurityPolicies) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SecurityPolicy, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(securitypoliciesResource, c.ns, name, pt, data, subresources...), &v1alpha1.SecurityPolicy{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SecurityPolicy), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_staticroute.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_staticroute.go new file mode 100644 index 000000000..d6a0a0396 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_staticroute.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeStaticRoutes implements StaticRouteInterface +type FakeStaticRoutes struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var staticroutesResource = v1alpha1.SchemeGroupVersion.WithResource("staticroutes") + +var staticroutesKind = v1alpha1.SchemeGroupVersion.WithKind("StaticRoute") + +// Get takes name of the staticRoute, and returns the corresponding staticRoute object, and an error if there is any. +func (c *FakeStaticRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.StaticRoute, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(staticroutesResource, c.ns, name), &v1alpha1.StaticRoute{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.StaticRoute), err +} + +// List takes label and field selectors, and returns the list of StaticRoutes that match those selectors. +func (c *FakeStaticRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.StaticRouteList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(staticroutesResource, staticroutesKind, c.ns, opts), &v1alpha1.StaticRouteList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.StaticRouteList{ListMeta: obj.(*v1alpha1.StaticRouteList).ListMeta} + for _, item := range obj.(*v1alpha1.StaticRouteList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested staticRoutes. +func (c *FakeStaticRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(staticroutesResource, c.ns, opts)) + +} + +// Create takes the representation of a staticRoute and creates it. Returns the server's representation of the staticRoute, and an error, if there is any. +func (c *FakeStaticRoutes) Create(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.CreateOptions) (result *v1alpha1.StaticRoute, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(staticroutesResource, c.ns, staticRoute), &v1alpha1.StaticRoute{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.StaticRoute), err +} + +// Update takes the representation of a staticRoute and updates it. Returns the server's representation of the staticRoute, and an error, if there is any. +func (c *FakeStaticRoutes) Update(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.UpdateOptions) (result *v1alpha1.StaticRoute, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(staticroutesResource, c.ns, staticRoute), &v1alpha1.StaticRoute{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.StaticRoute), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeStaticRoutes) UpdateStatus(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.UpdateOptions) (*v1alpha1.StaticRoute, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(staticroutesResource, "status", c.ns, staticRoute), &v1alpha1.StaticRoute{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.StaticRoute), err +} + +// Delete takes name of the staticRoute and deletes it. Returns an error if one occurs. +func (c *FakeStaticRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(staticroutesResource, c.ns, name, opts), &v1alpha1.StaticRoute{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeStaticRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(staticroutesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.StaticRouteList{}) + return err +} + +// Patch applies the patch and returns the patched staticRoute. +func (c *FakeStaticRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.StaticRoute, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(staticroutesResource, c.ns, name, pt, data, subresources...), &v1alpha1.StaticRoute{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.StaticRoute), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnet.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnet.go new file mode 100644 index 000000000..46984a34d --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnet.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeSubnets implements SubnetInterface +type FakeSubnets struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var subnetsResource = v1alpha1.SchemeGroupVersion.WithResource("subnets") + +var subnetsKind = v1alpha1.SchemeGroupVersion.WithKind("Subnet") + +// Get takes name of the subnet, and returns the corresponding subnet object, and an error if there is any. +func (c *FakeSubnets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Subnet, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(subnetsResource, c.ns, name), &v1alpha1.Subnet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subnet), err +} + +// List takes label and field selectors, and returns the list of Subnets that match those selectors. +func (c *FakeSubnets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SubnetList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(subnetsResource, subnetsKind, c.ns, opts), &v1alpha1.SubnetList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.SubnetList{ListMeta: obj.(*v1alpha1.SubnetList).ListMeta} + for _, item := range obj.(*v1alpha1.SubnetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested subnets. +func (c *FakeSubnets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(subnetsResource, c.ns, opts)) + +} + +// Create takes the representation of a subnet and creates it. Returns the server's representation of the subnet, and an error, if there is any. +func (c *FakeSubnets) Create(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.CreateOptions) (result *v1alpha1.Subnet, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(subnetsResource, c.ns, subnet), &v1alpha1.Subnet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subnet), err +} + +// Update takes the representation of a subnet and updates it. Returns the server's representation of the subnet, and an error, if there is any. +func (c *FakeSubnets) Update(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.UpdateOptions) (result *v1alpha1.Subnet, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(subnetsResource, c.ns, subnet), &v1alpha1.Subnet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subnet), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeSubnets) UpdateStatus(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.UpdateOptions) (*v1alpha1.Subnet, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(subnetsResource, "status", c.ns, subnet), &v1alpha1.Subnet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subnet), err +} + +// Delete takes name of the subnet and deletes it. Returns an error if one occurs. +func (c *FakeSubnets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(subnetsResource, c.ns, name, opts), &v1alpha1.Subnet{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSubnets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(subnetsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.SubnetList{}) + return err +} + +// Patch applies the patch and returns the patched subnet. +func (c *FakeSubnets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Subnet, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(subnetsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Subnet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subnet), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnetport.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnetport.go new file mode 100644 index 000000000..e9d418778 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnetport.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeSubnetPorts implements SubnetPortInterface +type FakeSubnetPorts struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var subnetportsResource = v1alpha1.SchemeGroupVersion.WithResource("subnetports") + +var subnetportsKind = v1alpha1.SchemeGroupVersion.WithKind("SubnetPort") + +// Get takes name of the subnetPort, and returns the corresponding subnetPort object, and an error if there is any. +func (c *FakeSubnetPorts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SubnetPort, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(subnetportsResource, c.ns, name), &v1alpha1.SubnetPort{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetPort), err +} + +// List takes label and field selectors, and returns the list of SubnetPorts that match those selectors. +func (c *FakeSubnetPorts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SubnetPortList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(subnetportsResource, subnetportsKind, c.ns, opts), &v1alpha1.SubnetPortList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.SubnetPortList{ListMeta: obj.(*v1alpha1.SubnetPortList).ListMeta} + for _, item := range obj.(*v1alpha1.SubnetPortList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested subnetPorts. +func (c *FakeSubnetPorts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(subnetportsResource, c.ns, opts)) + +} + +// Create takes the representation of a subnetPort and creates it. Returns the server's representation of the subnetPort, and an error, if there is any. +func (c *FakeSubnetPorts) Create(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.CreateOptions) (result *v1alpha1.SubnetPort, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(subnetportsResource, c.ns, subnetPort), &v1alpha1.SubnetPort{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetPort), err +} + +// Update takes the representation of a subnetPort and updates it. Returns the server's representation of the subnetPort, and an error, if there is any. +func (c *FakeSubnetPorts) Update(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.UpdateOptions) (result *v1alpha1.SubnetPort, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(subnetportsResource, c.ns, subnetPort), &v1alpha1.SubnetPort{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetPort), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeSubnetPorts) UpdateStatus(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.UpdateOptions) (*v1alpha1.SubnetPort, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(subnetportsResource, "status", c.ns, subnetPort), &v1alpha1.SubnetPort{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetPort), err +} + +// Delete takes name of the subnetPort and deletes it. Returns an error if one occurs. +func (c *FakeSubnetPorts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(subnetportsResource, c.ns, name, opts), &v1alpha1.SubnetPort{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSubnetPorts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(subnetportsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.SubnetPortList{}) + return err +} + +// Patch applies the patch and returns the patched subnetPort. +func (c *FakeSubnetPorts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SubnetPort, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(subnetportsResource, c.ns, name, pt, data, subresources...), &v1alpha1.SubnetPort{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetPort), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnetset.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnetset.go new file mode 100644 index 000000000..6d0aa0ee7 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_subnetset.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeSubnetSets implements SubnetSetInterface +type FakeSubnetSets struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var subnetsetsResource = v1alpha1.SchemeGroupVersion.WithResource("subnetsets") + +var subnetsetsKind = v1alpha1.SchemeGroupVersion.WithKind("SubnetSet") + +// Get takes name of the subnetSet, and returns the corresponding subnetSet object, and an error if there is any. +func (c *FakeSubnetSets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SubnetSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(subnetsetsResource, c.ns, name), &v1alpha1.SubnetSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetSet), err +} + +// List takes label and field selectors, and returns the list of SubnetSets that match those selectors. +func (c *FakeSubnetSets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SubnetSetList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(subnetsetsResource, subnetsetsKind, c.ns, opts), &v1alpha1.SubnetSetList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.SubnetSetList{ListMeta: obj.(*v1alpha1.SubnetSetList).ListMeta} + for _, item := range obj.(*v1alpha1.SubnetSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested subnetSets. +func (c *FakeSubnetSets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(subnetsetsResource, c.ns, opts)) + +} + +// Create takes the representation of a subnetSet and creates it. Returns the server's representation of the subnetSet, and an error, if there is any. +func (c *FakeSubnetSets) Create(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.CreateOptions) (result *v1alpha1.SubnetSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(subnetsetsResource, c.ns, subnetSet), &v1alpha1.SubnetSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetSet), err +} + +// Update takes the representation of a subnetSet and updates it. Returns the server's representation of the subnetSet, and an error, if there is any. +func (c *FakeSubnetSets) Update(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.UpdateOptions) (result *v1alpha1.SubnetSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(subnetsetsResource, c.ns, subnetSet), &v1alpha1.SubnetSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetSet), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeSubnetSets) UpdateStatus(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.UpdateOptions) (*v1alpha1.SubnetSet, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(subnetsetsResource, "status", c.ns, subnetSet), &v1alpha1.SubnetSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetSet), err +} + +// Delete takes name of the subnetSet and deletes it. Returns an error if one occurs. +func (c *FakeSubnetSets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(subnetsetsResource, c.ns, name, opts), &v1alpha1.SubnetSet{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSubnetSets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(subnetsetsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.SubnetSetList{}) + return err +} + +// Patch applies the patch and returns the patched subnetSet. +func (c *FakeSubnetSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SubnetSet, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(subnetsetsResource, c.ns, name, pt, data, subresources...), &v1alpha1.SubnetSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SubnetSet), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_vpc.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_vpc.go new file mode 100644 index 000000000..0899713ad --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_vpc.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeVPCs implements VPCInterface +type FakeVPCs struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var vpcsResource = v1alpha1.SchemeGroupVersion.WithResource("vpcs") + +var vpcsKind = v1alpha1.SchemeGroupVersion.WithKind("VPC") + +// Get takes name of the vPC, and returns the corresponding vPC object, and an error if there is any. +func (c *FakeVPCs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.VPC, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(vpcsResource, c.ns, name), &v1alpha1.VPC{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPC), err +} + +// List takes label and field selectors, and returns the list of VPCs that match those selectors. +func (c *FakeVPCs) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.VPCList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(vpcsResource, vpcsKind, c.ns, opts), &v1alpha1.VPCList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.VPCList{ListMeta: obj.(*v1alpha1.VPCList).ListMeta} + for _, item := range obj.(*v1alpha1.VPCList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested vPCs. +func (c *FakeVPCs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(vpcsResource, c.ns, opts)) + +} + +// Create takes the representation of a vPC and creates it. Returns the server's representation of the vPC, and an error, if there is any. +func (c *FakeVPCs) Create(ctx context.Context, vPC *v1alpha1.VPC, opts v1.CreateOptions) (result *v1alpha1.VPC, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(vpcsResource, c.ns, vPC), &v1alpha1.VPC{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPC), err +} + +// Update takes the representation of a vPC and updates it. Returns the server's representation of the vPC, and an error, if there is any. +func (c *FakeVPCs) Update(ctx context.Context, vPC *v1alpha1.VPC, opts v1.UpdateOptions) (result *v1alpha1.VPC, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(vpcsResource, c.ns, vPC), &v1alpha1.VPC{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPC), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeVPCs) UpdateStatus(ctx context.Context, vPC *v1alpha1.VPC, opts v1.UpdateOptions) (*v1alpha1.VPC, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(vpcsResource, "status", c.ns, vPC), &v1alpha1.VPC{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPC), err +} + +// Delete takes name of the vPC and deletes it. Returns an error if one occurs. +func (c *FakeVPCs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(vpcsResource, c.ns, name, opts), &v1alpha1.VPC{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVPCs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(vpcsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.VPCList{}) + return err +} + +// Patch applies the patch and returns the patched vPC. +func (c *FakeVPCs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.VPC, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(vpcsResource, c.ns, name, pt, data, subresources...), &v1alpha1.VPC{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPC), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_vpcnetworkconfiguration.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_vpcnetworkconfiguration.go new file mode 100644 index 000000000..0ff8f05ff --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/fake/fake_vpcnetworkconfiguration.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeVPCNetworkConfigurations implements VPCNetworkConfigurationInterface +type FakeVPCNetworkConfigurations struct { + Fake *FakeNsxV1alpha1 + ns string +} + +var vpcnetworkconfigurationsResource = v1alpha1.SchemeGroupVersion.WithResource("vpcnetworkconfigurations") + +var vpcnetworkconfigurationsKind = v1alpha1.SchemeGroupVersion.WithKind("VPCNetworkConfiguration") + +// Get takes name of the vPCNetworkConfiguration, and returns the corresponding vPCNetworkConfiguration object, and an error if there is any. +func (c *FakeVPCNetworkConfigurations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(vpcnetworkconfigurationsResource, c.ns, name), &v1alpha1.VPCNetworkConfiguration{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPCNetworkConfiguration), err +} + +// List takes label and field selectors, and returns the list of VPCNetworkConfigurations that match those selectors. +func (c *FakeVPCNetworkConfigurations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.VPCNetworkConfigurationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(vpcnetworkconfigurationsResource, vpcnetworkconfigurationsKind, c.ns, opts), &v1alpha1.VPCNetworkConfigurationList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.VPCNetworkConfigurationList{ListMeta: obj.(*v1alpha1.VPCNetworkConfigurationList).ListMeta} + for _, item := range obj.(*v1alpha1.VPCNetworkConfigurationList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested vPCNetworkConfigurations. +func (c *FakeVPCNetworkConfigurations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(vpcnetworkconfigurationsResource, c.ns, opts)) + +} + +// Create takes the representation of a vPCNetworkConfiguration and creates it. Returns the server's representation of the vPCNetworkConfiguration, and an error, if there is any. +func (c *FakeVPCNetworkConfigurations) Create(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.CreateOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(vpcnetworkconfigurationsResource, c.ns, vPCNetworkConfiguration), &v1alpha1.VPCNetworkConfiguration{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPCNetworkConfiguration), err +} + +// Update takes the representation of a vPCNetworkConfiguration and updates it. Returns the server's representation of the vPCNetworkConfiguration, and an error, if there is any. +func (c *FakeVPCNetworkConfigurations) Update(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.UpdateOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(vpcnetworkconfigurationsResource, c.ns, vPCNetworkConfiguration), &v1alpha1.VPCNetworkConfiguration{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPCNetworkConfiguration), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeVPCNetworkConfigurations) UpdateStatus(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.UpdateOptions) (*v1alpha1.VPCNetworkConfiguration, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(vpcnetworkconfigurationsResource, "status", c.ns, vPCNetworkConfiguration), &v1alpha1.VPCNetworkConfiguration{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPCNetworkConfiguration), err +} + +// Delete takes name of the vPCNetworkConfiguration and deletes it. Returns an error if one occurs. +func (c *FakeVPCNetworkConfigurations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(vpcnetworkconfigurationsResource, c.ns, name, opts), &v1alpha1.VPCNetworkConfiguration{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVPCNetworkConfigurations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(vpcnetworkconfigurationsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.VPCNetworkConfigurationList{}) + return err +} + +// Patch applies the patch and returns the patched vPCNetworkConfiguration. +func (c *FakeVPCNetworkConfigurations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.VPCNetworkConfiguration, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(vpcnetworkconfigurationsResource, c.ns, name, pt, data, subresources...), &v1alpha1.VPCNetworkConfiguration{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.VPCNetworkConfiguration), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/generated_expansion.go new file mode 100644 index 000000000..4d33a8ca5 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/generated_expansion.go @@ -0,0 +1,24 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type IPPoolExpansion interface{} + +type NSXServiceAccountExpansion interface{} + +type SecurityPolicyExpansion interface{} + +type StaticRouteExpansion interface{} + +type SubnetExpansion interface{} + +type SubnetPortExpansion interface{} + +type SubnetSetExpansion interface{} + +type VPCExpansion interface{} + +type VPCNetworkConfigurationExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/ippool.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/ippool.go new file mode 100644 index 000000000..f9d995fbc --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/ippool.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// IPPoolsGetter has a method to return a IPPoolInterface. +// A group's client should implement this interface. +type IPPoolsGetter interface { + IPPools(namespace string) IPPoolInterface +} + +// IPPoolInterface has methods to work with IPPool resources. +type IPPoolInterface interface { + Create(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.CreateOptions) (*v1alpha1.IPPool, error) + Update(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.UpdateOptions) (*v1alpha1.IPPool, error) + UpdateStatus(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.UpdateOptions) (*v1alpha1.IPPool, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.IPPool, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.IPPoolList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPPool, err error) + IPPoolExpansion +} + +// iPPools implements IPPoolInterface +type iPPools struct { + client rest.Interface + ns string +} + +// newIPPools returns a IPPools +func newIPPools(c *NsxV1alpha1Client, namespace string) *iPPools { + return &iPPools{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the iPPool, and returns the corresponding iPPool object, and an error if there is any. +func (c *iPPools) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IPPool, err error) { + result = &v1alpha1.IPPool{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ippools"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of IPPools that match those selectors. +func (c *iPPools) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IPPoolList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.IPPoolList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested iPPools. +func (c *iPPools) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a iPPool and creates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *iPPools) Create(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.CreateOptions) (result *v1alpha1.IPPool, err error) { + result = &v1alpha1.IPPool{} + err = c.client.Post(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(iPPool). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a iPPool and updates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *iPPools) Update(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.UpdateOptions) (result *v1alpha1.IPPool, err error) { + result = &v1alpha1.IPPool{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ippools"). + Name(iPPool.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(iPPool). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *iPPools) UpdateStatus(ctx context.Context, iPPool *v1alpha1.IPPool, opts v1.UpdateOptions) (result *v1alpha1.IPPool, err error) { + result = &v1alpha1.IPPool{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ippools"). + Name(iPPool.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(iPPool). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the iPPool and deletes it. Returns an error if one occurs. +func (c *iPPools) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ippools"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *iPPools) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched iPPool. +func (c *iPPools) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPPool, err error) { + result = &v1alpha1.IPPool{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("ippools"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/nsx.vmware.com_client.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/nsx.vmware.com_client.go new file mode 100644 index 000000000..18e2b5393 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/nsx.vmware.com_client.go @@ -0,0 +1,134 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "net/http" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type NsxV1alpha1Interface interface { + RESTClient() rest.Interface + IPPoolsGetter + NSXServiceAccountsGetter + SecurityPoliciesGetter + StaticRoutesGetter + SubnetsGetter + SubnetPortsGetter + SubnetSetsGetter + VPCsGetter + VPCNetworkConfigurationsGetter +} + +// NsxV1alpha1Client is used to interact with features provided by the nsx.vmware.com group. +type NsxV1alpha1Client struct { + restClient rest.Interface +} + +func (c *NsxV1alpha1Client) IPPools(namespace string) IPPoolInterface { + return newIPPools(c, namespace) +} + +func (c *NsxV1alpha1Client) NSXServiceAccounts(namespace string) NSXServiceAccountInterface { + return newNSXServiceAccounts(c, namespace) +} + +func (c *NsxV1alpha1Client) SecurityPolicies(namespace string) SecurityPolicyInterface { + return newSecurityPolicies(c, namespace) +} + +func (c *NsxV1alpha1Client) StaticRoutes(namespace string) StaticRouteInterface { + return newStaticRoutes(c, namespace) +} + +func (c *NsxV1alpha1Client) Subnets(namespace string) SubnetInterface { + return newSubnets(c, namespace) +} + +func (c *NsxV1alpha1Client) SubnetPorts(namespace string) SubnetPortInterface { + return newSubnetPorts(c, namespace) +} + +func (c *NsxV1alpha1Client) SubnetSets(namespace string) SubnetSetInterface { + return newSubnetSets(c, namespace) +} + +func (c *NsxV1alpha1Client) VPCs(namespace string) VPCInterface { + return newVPCs(c, namespace) +} + +func (c *NsxV1alpha1Client) VPCNetworkConfigurations(namespace string) VPCNetworkConfigurationInterface { + return newVPCNetworkConfigurations(c, namespace) +} + +// NewForConfig creates a new NsxV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*NsxV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new NsxV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*NsxV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &NsxV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new NsxV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *NsxV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new NsxV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *NsxV1alpha1Client { + return &NsxV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *NsxV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/nsxserviceaccount.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/nsxserviceaccount.go new file mode 100644 index 000000000..4cf6075b5 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/nsxserviceaccount.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// NSXServiceAccountsGetter has a method to return a NSXServiceAccountInterface. +// A group's client should implement this interface. +type NSXServiceAccountsGetter interface { + NSXServiceAccounts(namespace string) NSXServiceAccountInterface +} + +// NSXServiceAccountInterface has methods to work with NSXServiceAccount resources. +type NSXServiceAccountInterface interface { + Create(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.CreateOptions) (*v1alpha1.NSXServiceAccount, error) + Update(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.UpdateOptions) (*v1alpha1.NSXServiceAccount, error) + UpdateStatus(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.UpdateOptions) (*v1alpha1.NSXServiceAccount, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.NSXServiceAccount, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.NSXServiceAccountList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NSXServiceAccount, err error) + NSXServiceAccountExpansion +} + +// nSXServiceAccounts implements NSXServiceAccountInterface +type nSXServiceAccounts struct { + client rest.Interface + ns string +} + +// newNSXServiceAccounts returns a NSXServiceAccounts +func newNSXServiceAccounts(c *NsxV1alpha1Client, namespace string) *nSXServiceAccounts { + return &nSXServiceAccounts{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the nSXServiceAccount, and returns the corresponding nSXServiceAccount object, and an error if there is any. +func (c *nSXServiceAccounts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.NSXServiceAccount, err error) { + result = &v1alpha1.NSXServiceAccount{} + err = c.client.Get(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of NSXServiceAccounts that match those selectors. +func (c *nSXServiceAccounts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NSXServiceAccountList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.NSXServiceAccountList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested nSXServiceAccounts. +func (c *nSXServiceAccounts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a nSXServiceAccount and creates it. Returns the server's representation of the nSXServiceAccount, and an error, if there is any. +func (c *nSXServiceAccounts) Create(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.CreateOptions) (result *v1alpha1.NSXServiceAccount, err error) { + result = &v1alpha1.NSXServiceAccount{} + err = c.client.Post(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(nSXServiceAccount). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a nSXServiceAccount and updates it. Returns the server's representation of the nSXServiceAccount, and an error, if there is any. +func (c *nSXServiceAccounts) Update(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.UpdateOptions) (result *v1alpha1.NSXServiceAccount, err error) { + result = &v1alpha1.NSXServiceAccount{} + err = c.client.Put(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + Name(nSXServiceAccount.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(nSXServiceAccount). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *nSXServiceAccounts) UpdateStatus(ctx context.Context, nSXServiceAccount *v1alpha1.NSXServiceAccount, opts v1.UpdateOptions) (result *v1alpha1.NSXServiceAccount, err error) { + result = &v1alpha1.NSXServiceAccount{} + err = c.client.Put(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + Name(nSXServiceAccount.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(nSXServiceAccount). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the nSXServiceAccount and deletes it. Returns an error if one occurs. +func (c *nSXServiceAccounts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *nSXServiceAccounts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched nSXServiceAccount. +func (c *nSXServiceAccounts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NSXServiceAccount, err error) { + result = &v1alpha1.NSXServiceAccount{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("nsxserviceaccounts"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/securitypolicy.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/securitypolicy.go new file mode 100644 index 000000000..a4414f02a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/securitypolicy.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// SecurityPoliciesGetter has a method to return a SecurityPolicyInterface. +// A group's client should implement this interface. +type SecurityPoliciesGetter interface { + SecurityPolicies(namespace string) SecurityPolicyInterface +} + +// SecurityPolicyInterface has methods to work with SecurityPolicy resources. +type SecurityPolicyInterface interface { + Create(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.CreateOptions) (*v1alpha1.SecurityPolicy, error) + Update(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.UpdateOptions) (*v1alpha1.SecurityPolicy, error) + UpdateStatus(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.UpdateOptions) (*v1alpha1.SecurityPolicy, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.SecurityPolicy, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.SecurityPolicyList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SecurityPolicy, err error) + SecurityPolicyExpansion +} + +// securityPolicies implements SecurityPolicyInterface +type securityPolicies struct { + client rest.Interface + ns string +} + +// newSecurityPolicies returns a SecurityPolicies +func newSecurityPolicies(c *NsxV1alpha1Client, namespace string) *securityPolicies { + return &securityPolicies{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the securityPolicy, and returns the corresponding securityPolicy object, and an error if there is any. +func (c *securityPolicies) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SecurityPolicy, err error) { + result = &v1alpha1.SecurityPolicy{} + err = c.client.Get(). + Namespace(c.ns). + Resource("securitypolicies"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SecurityPolicies that match those selectors. +func (c *securityPolicies) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SecurityPolicyList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.SecurityPolicyList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("securitypolicies"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested securityPolicies. +func (c *securityPolicies) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("securitypolicies"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a securityPolicy and creates it. Returns the server's representation of the securityPolicy, and an error, if there is any. +func (c *securityPolicies) Create(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.CreateOptions) (result *v1alpha1.SecurityPolicy, err error) { + result = &v1alpha1.SecurityPolicy{} + err = c.client.Post(). + Namespace(c.ns). + Resource("securitypolicies"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(securityPolicy). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a securityPolicy and updates it. Returns the server's representation of the securityPolicy, and an error, if there is any. +func (c *securityPolicies) Update(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.UpdateOptions) (result *v1alpha1.SecurityPolicy, err error) { + result = &v1alpha1.SecurityPolicy{} + err = c.client.Put(). + Namespace(c.ns). + Resource("securitypolicies"). + Name(securityPolicy.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(securityPolicy). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *securityPolicies) UpdateStatus(ctx context.Context, securityPolicy *v1alpha1.SecurityPolicy, opts v1.UpdateOptions) (result *v1alpha1.SecurityPolicy, err error) { + result = &v1alpha1.SecurityPolicy{} + err = c.client.Put(). + Namespace(c.ns). + Resource("securitypolicies"). + Name(securityPolicy.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(securityPolicy). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the securityPolicy and deletes it. Returns an error if one occurs. +func (c *securityPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("securitypolicies"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *securityPolicies) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("securitypolicies"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched securityPolicy. +func (c *securityPolicies) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SecurityPolicy, err error) { + result = &v1alpha1.SecurityPolicy{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("securitypolicies"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/staticroute.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/staticroute.go new file mode 100644 index 000000000..6c1b7f0f6 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/staticroute.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// StaticRoutesGetter has a method to return a StaticRouteInterface. +// A group's client should implement this interface. +type StaticRoutesGetter interface { + StaticRoutes(namespace string) StaticRouteInterface +} + +// StaticRouteInterface has methods to work with StaticRoute resources. +type StaticRouteInterface interface { + Create(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.CreateOptions) (*v1alpha1.StaticRoute, error) + Update(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.UpdateOptions) (*v1alpha1.StaticRoute, error) + UpdateStatus(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.UpdateOptions) (*v1alpha1.StaticRoute, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.StaticRoute, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.StaticRouteList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.StaticRoute, err error) + StaticRouteExpansion +} + +// staticRoutes implements StaticRouteInterface +type staticRoutes struct { + client rest.Interface + ns string +} + +// newStaticRoutes returns a StaticRoutes +func newStaticRoutes(c *NsxV1alpha1Client, namespace string) *staticRoutes { + return &staticRoutes{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the staticRoute, and returns the corresponding staticRoute object, and an error if there is any. +func (c *staticRoutes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.StaticRoute, err error) { + result = &v1alpha1.StaticRoute{} + err = c.client.Get(). + Namespace(c.ns). + Resource("staticroutes"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of StaticRoutes that match those selectors. +func (c *staticRoutes) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.StaticRouteList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.StaticRouteList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("staticroutes"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested staticRoutes. +func (c *staticRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("staticroutes"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a staticRoute and creates it. Returns the server's representation of the staticRoute, and an error, if there is any. +func (c *staticRoutes) Create(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.CreateOptions) (result *v1alpha1.StaticRoute, err error) { + result = &v1alpha1.StaticRoute{} + err = c.client.Post(). + Namespace(c.ns). + Resource("staticroutes"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(staticRoute). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a staticRoute and updates it. Returns the server's representation of the staticRoute, and an error, if there is any. +func (c *staticRoutes) Update(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.UpdateOptions) (result *v1alpha1.StaticRoute, err error) { + result = &v1alpha1.StaticRoute{} + err = c.client.Put(). + Namespace(c.ns). + Resource("staticroutes"). + Name(staticRoute.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(staticRoute). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *staticRoutes) UpdateStatus(ctx context.Context, staticRoute *v1alpha1.StaticRoute, opts v1.UpdateOptions) (result *v1alpha1.StaticRoute, err error) { + result = &v1alpha1.StaticRoute{} + err = c.client.Put(). + Namespace(c.ns). + Resource("staticroutes"). + Name(staticRoute.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(staticRoute). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the staticRoute and deletes it. Returns an error if one occurs. +func (c *staticRoutes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("staticroutes"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *staticRoutes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("staticroutes"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched staticRoute. +func (c *staticRoutes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.StaticRoute, err error) { + result = &v1alpha1.StaticRoute{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("staticroutes"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnet.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnet.go new file mode 100644 index 000000000..c8fed4878 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnet.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// SubnetsGetter has a method to return a SubnetInterface. +// A group's client should implement this interface. +type SubnetsGetter interface { + Subnets(namespace string) SubnetInterface +} + +// SubnetInterface has methods to work with Subnet resources. +type SubnetInterface interface { + Create(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.CreateOptions) (*v1alpha1.Subnet, error) + Update(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.UpdateOptions) (*v1alpha1.Subnet, error) + UpdateStatus(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.UpdateOptions) (*v1alpha1.Subnet, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Subnet, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.SubnetList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Subnet, err error) + SubnetExpansion +} + +// subnets implements SubnetInterface +type subnets struct { + client rest.Interface + ns string +} + +// newSubnets returns a Subnets +func newSubnets(c *NsxV1alpha1Client, namespace string) *subnets { + return &subnets{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the subnet, and returns the corresponding subnet object, and an error if there is any. +func (c *subnets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Subnet, err error) { + result = &v1alpha1.Subnet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subnets"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Subnets that match those selectors. +func (c *subnets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SubnetList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.SubnetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subnets"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested subnets. +func (c *subnets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("subnets"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a subnet and creates it. Returns the server's representation of the subnet, and an error, if there is any. +func (c *subnets) Create(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.CreateOptions) (result *v1alpha1.Subnet, err error) { + result = &v1alpha1.Subnet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("subnets"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnet). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a subnet and updates it. Returns the server's representation of the subnet, and an error, if there is any. +func (c *subnets) Update(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.UpdateOptions) (result *v1alpha1.Subnet, err error) { + result = &v1alpha1.Subnet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subnets"). + Name(subnet.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnet). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *subnets) UpdateStatus(ctx context.Context, subnet *v1alpha1.Subnet, opts v1.UpdateOptions) (result *v1alpha1.Subnet, err error) { + result = &v1alpha1.Subnet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subnets"). + Name(subnet.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnet). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the subnet and deletes it. Returns an error if one occurs. +func (c *subnets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("subnets"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *subnets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("subnets"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched subnet. +func (c *subnets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Subnet, err error) { + result = &v1alpha1.Subnet{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("subnets"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnetport.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnetport.go new file mode 100644 index 000000000..b67c408f9 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnetport.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// SubnetPortsGetter has a method to return a SubnetPortInterface. +// A group's client should implement this interface. +type SubnetPortsGetter interface { + SubnetPorts(namespace string) SubnetPortInterface +} + +// SubnetPortInterface has methods to work with SubnetPort resources. +type SubnetPortInterface interface { + Create(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.CreateOptions) (*v1alpha1.SubnetPort, error) + Update(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.UpdateOptions) (*v1alpha1.SubnetPort, error) + UpdateStatus(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.UpdateOptions) (*v1alpha1.SubnetPort, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.SubnetPort, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.SubnetPortList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SubnetPort, err error) + SubnetPortExpansion +} + +// subnetPorts implements SubnetPortInterface +type subnetPorts struct { + client rest.Interface + ns string +} + +// newSubnetPorts returns a SubnetPorts +func newSubnetPorts(c *NsxV1alpha1Client, namespace string) *subnetPorts { + return &subnetPorts{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the subnetPort, and returns the corresponding subnetPort object, and an error if there is any. +func (c *subnetPorts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SubnetPort, err error) { + result = &v1alpha1.SubnetPort{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subnetports"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SubnetPorts that match those selectors. +func (c *subnetPorts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SubnetPortList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.SubnetPortList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subnetports"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested subnetPorts. +func (c *subnetPorts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("subnetports"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a subnetPort and creates it. Returns the server's representation of the subnetPort, and an error, if there is any. +func (c *subnetPorts) Create(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.CreateOptions) (result *v1alpha1.SubnetPort, err error) { + result = &v1alpha1.SubnetPort{} + err = c.client.Post(). + Namespace(c.ns). + Resource("subnetports"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnetPort). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a subnetPort and updates it. Returns the server's representation of the subnetPort, and an error, if there is any. +func (c *subnetPorts) Update(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.UpdateOptions) (result *v1alpha1.SubnetPort, err error) { + result = &v1alpha1.SubnetPort{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subnetports"). + Name(subnetPort.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnetPort). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *subnetPorts) UpdateStatus(ctx context.Context, subnetPort *v1alpha1.SubnetPort, opts v1.UpdateOptions) (result *v1alpha1.SubnetPort, err error) { + result = &v1alpha1.SubnetPort{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subnetports"). + Name(subnetPort.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnetPort). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the subnetPort and deletes it. Returns an error if one occurs. +func (c *subnetPorts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("subnetports"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *subnetPorts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("subnetports"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched subnetPort. +func (c *subnetPorts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SubnetPort, err error) { + result = &v1alpha1.SubnetPort{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("subnetports"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnetset.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnetset.go new file mode 100644 index 000000000..b5bd0ee05 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/subnetset.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// SubnetSetsGetter has a method to return a SubnetSetInterface. +// A group's client should implement this interface. +type SubnetSetsGetter interface { + SubnetSets(namespace string) SubnetSetInterface +} + +// SubnetSetInterface has methods to work with SubnetSet resources. +type SubnetSetInterface interface { + Create(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.CreateOptions) (*v1alpha1.SubnetSet, error) + Update(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.UpdateOptions) (*v1alpha1.SubnetSet, error) + UpdateStatus(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.UpdateOptions) (*v1alpha1.SubnetSet, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.SubnetSet, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.SubnetSetList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SubnetSet, err error) + SubnetSetExpansion +} + +// subnetSets implements SubnetSetInterface +type subnetSets struct { + client rest.Interface + ns string +} + +// newSubnetSets returns a SubnetSets +func newSubnetSets(c *NsxV1alpha1Client, namespace string) *subnetSets { + return &subnetSets{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the subnetSet, and returns the corresponding subnetSet object, and an error if there is any. +func (c *subnetSets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SubnetSet, err error) { + result = &v1alpha1.SubnetSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subnetsets"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SubnetSets that match those selectors. +func (c *subnetSets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SubnetSetList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.SubnetSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subnetsets"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested subnetSets. +func (c *subnetSets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("subnetsets"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a subnetSet and creates it. Returns the server's representation of the subnetSet, and an error, if there is any. +func (c *subnetSets) Create(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.CreateOptions) (result *v1alpha1.SubnetSet, err error) { + result = &v1alpha1.SubnetSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("subnetsets"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnetSet). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a subnetSet and updates it. Returns the server's representation of the subnetSet, and an error, if there is any. +func (c *subnetSets) Update(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.UpdateOptions) (result *v1alpha1.SubnetSet, err error) { + result = &v1alpha1.SubnetSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subnetsets"). + Name(subnetSet.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnetSet). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *subnetSets) UpdateStatus(ctx context.Context, subnetSet *v1alpha1.SubnetSet, opts v1.UpdateOptions) (result *v1alpha1.SubnetSet, err error) { + result = &v1alpha1.SubnetSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subnetsets"). + Name(subnetSet.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subnetSet). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the subnetSet and deletes it. Returns an error if one occurs. +func (c *subnetSets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("subnetsets"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *subnetSets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("subnetsets"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched subnetSet. +func (c *subnetSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SubnetSet, err error) { + result = &v1alpha1.SubnetSet{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("subnetsets"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/vpc.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/vpc.go new file mode 100644 index 000000000..a5ff30581 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/vpc.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// VPCsGetter has a method to return a VPCInterface. +// A group's client should implement this interface. +type VPCsGetter interface { + VPCs(namespace string) VPCInterface +} + +// VPCInterface has methods to work with VPC resources. +type VPCInterface interface { + Create(ctx context.Context, vPC *v1alpha1.VPC, opts v1.CreateOptions) (*v1alpha1.VPC, error) + Update(ctx context.Context, vPC *v1alpha1.VPC, opts v1.UpdateOptions) (*v1alpha1.VPC, error) + UpdateStatus(ctx context.Context, vPC *v1alpha1.VPC, opts v1.UpdateOptions) (*v1alpha1.VPC, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.VPC, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.VPCList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.VPC, err error) + VPCExpansion +} + +// vPCs implements VPCInterface +type vPCs struct { + client rest.Interface + ns string +} + +// newVPCs returns a VPCs +func newVPCs(c *NsxV1alpha1Client, namespace string) *vPCs { + return &vPCs{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the vPC, and returns the corresponding vPC object, and an error if there is any. +func (c *vPCs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.VPC, err error) { + result = &v1alpha1.VPC{} + err = c.client.Get(). + Namespace(c.ns). + Resource("vpcs"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VPCs that match those selectors. +func (c *vPCs) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.VPCList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.VPCList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("vpcs"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested vPCs. +func (c *vPCs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("vpcs"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a vPC and creates it. Returns the server's representation of the vPC, and an error, if there is any. +func (c *vPCs) Create(ctx context.Context, vPC *v1alpha1.VPC, opts v1.CreateOptions) (result *v1alpha1.VPC, err error) { + result = &v1alpha1.VPC{} + err = c.client.Post(). + Namespace(c.ns). + Resource("vpcs"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(vPC). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a vPC and updates it. Returns the server's representation of the vPC, and an error, if there is any. +func (c *vPCs) Update(ctx context.Context, vPC *v1alpha1.VPC, opts v1.UpdateOptions) (result *v1alpha1.VPC, err error) { + result = &v1alpha1.VPC{} + err = c.client.Put(). + Namespace(c.ns). + Resource("vpcs"). + Name(vPC.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(vPC). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *vPCs) UpdateStatus(ctx context.Context, vPC *v1alpha1.VPC, opts v1.UpdateOptions) (result *v1alpha1.VPC, err error) { + result = &v1alpha1.VPC{} + err = c.client.Put(). + Namespace(c.ns). + Resource("vpcs"). + Name(vPC.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(vPC). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the vPC and deletes it. Returns an error if one occurs. +func (c *vPCs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("vpcs"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *vPCs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("vpcs"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched vPC. +func (c *vPCs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.VPC, err error) { + result = &v1alpha1.VPC{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("vpcs"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go new file mode 100644 index 000000000..843eac9fb --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// VPCNetworkConfigurationsGetter has a method to return a VPCNetworkConfigurationInterface. +// A group's client should implement this interface. +type VPCNetworkConfigurationsGetter interface { + VPCNetworkConfigurations(namespace string) VPCNetworkConfigurationInterface +} + +// VPCNetworkConfigurationInterface has methods to work with VPCNetworkConfiguration resources. +type VPCNetworkConfigurationInterface interface { + Create(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.CreateOptions) (*v1alpha1.VPCNetworkConfiguration, error) + Update(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.UpdateOptions) (*v1alpha1.VPCNetworkConfiguration, error) + UpdateStatus(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.UpdateOptions) (*v1alpha1.VPCNetworkConfiguration, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.VPCNetworkConfiguration, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.VPCNetworkConfigurationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.VPCNetworkConfiguration, err error) + VPCNetworkConfigurationExpansion +} + +// vPCNetworkConfigurations implements VPCNetworkConfigurationInterface +type vPCNetworkConfigurations struct { + client rest.Interface + ns string +} + +// newVPCNetworkConfigurations returns a VPCNetworkConfigurations +func newVPCNetworkConfigurations(c *NsxV1alpha1Client, namespace string) *vPCNetworkConfigurations { + return &vPCNetworkConfigurations{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the vPCNetworkConfiguration, and returns the corresponding vPCNetworkConfiguration object, and an error if there is any. +func (c *vPCNetworkConfigurations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + result = &v1alpha1.VPCNetworkConfiguration{} + err = c.client.Get(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VPCNetworkConfigurations that match those selectors. +func (c *vPCNetworkConfigurations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.VPCNetworkConfigurationList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.VPCNetworkConfigurationList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested vPCNetworkConfigurations. +func (c *vPCNetworkConfigurations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a vPCNetworkConfiguration and creates it. Returns the server's representation of the vPCNetworkConfiguration, and an error, if there is any. +func (c *vPCNetworkConfigurations) Create(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.CreateOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + result = &v1alpha1.VPCNetworkConfiguration{} + err = c.client.Post(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(vPCNetworkConfiguration). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a vPCNetworkConfiguration and updates it. Returns the server's representation of the vPCNetworkConfiguration, and an error, if there is any. +func (c *vPCNetworkConfigurations) Update(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.UpdateOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + result = &v1alpha1.VPCNetworkConfiguration{} + err = c.client.Put(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + Name(vPCNetworkConfiguration.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(vPCNetworkConfiguration). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *vPCNetworkConfigurations) UpdateStatus(ctx context.Context, vPCNetworkConfiguration *v1alpha1.VPCNetworkConfiguration, opts v1.UpdateOptions) (result *v1alpha1.VPCNetworkConfiguration, err error) { + result = &v1alpha1.VPCNetworkConfiguration{} + err = c.client.Put(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + Name(vPCNetworkConfiguration.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(vPCNetworkConfiguration). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the vPCNetworkConfiguration and deletes it. Returns an error if one occurs. +func (c *vPCNetworkConfigurations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *vPCNetworkConfigurations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched vPCNetworkConfiguration. +func (c *vPCNetworkConfigurations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.VPCNetworkConfiguration, err error) { + result = &v1alpha1.VPCNetworkConfiguration{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("vpcnetworkconfigurations"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/doc.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/doc.go new file mode 100644 index 000000000..dee26b9f8 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/doc.go @@ -0,0 +1,7 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha2 diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/doc.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/doc.go new file mode 100644 index 000000000..f7c0364c0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/doc.go @@ -0,0 +1,7 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/fake_ippool.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/fake_ippool.go new file mode 100644 index 000000000..d442c1445 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/fake_ippool.go @@ -0,0 +1,128 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeIPPools implements IPPoolInterface +type FakeIPPools struct { + Fake *FakeNsxV1alpha2 + ns string +} + +var ippoolsResource = v1alpha2.SchemeGroupVersion.WithResource("ippools") + +var ippoolsKind = v1alpha2.SchemeGroupVersion.WithKind("IPPool") + +// Get takes name of the iPPool, and returns the corresponding iPPool object, and an error if there is any. +func (c *FakeIPPools) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(ippoolsResource, c.ns, name), &v1alpha2.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.IPPool), err +} + +// List takes label and field selectors, and returns the list of IPPools that match those selectors. +func (c *FakeIPPools) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.IPPoolList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(ippoolsResource, ippoolsKind, c.ns, opts), &v1alpha2.IPPoolList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.IPPoolList{ListMeta: obj.(*v1alpha2.IPPoolList).ListMeta} + for _, item := range obj.(*v1alpha2.IPPoolList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested iPPools. +func (c *FakeIPPools) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(ippoolsResource, c.ns, opts)) + +} + +// Create takes the representation of a iPPool and creates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *FakeIPPools) Create(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.CreateOptions) (result *v1alpha2.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(ippoolsResource, c.ns, iPPool), &v1alpha2.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.IPPool), err +} + +// Update takes the representation of a iPPool and updates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *FakeIPPools) Update(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.UpdateOptions) (result *v1alpha2.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(ippoolsResource, c.ns, iPPool), &v1alpha2.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.IPPool), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeIPPools) UpdateStatus(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.UpdateOptions) (*v1alpha2.IPPool, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(ippoolsResource, "status", c.ns, iPPool), &v1alpha2.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.IPPool), err +} + +// Delete takes name of the iPPool and deletes it. Returns an error if one occurs. +func (c *FakeIPPools) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(ippoolsResource, c.ns, name, opts), &v1alpha2.IPPool{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeIPPools) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(ippoolsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.IPPoolList{}) + return err +} + +// Patch applies the patch and returns the patched iPPool. +func (c *FakeIPPools) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.IPPool, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(ippoolsResource, c.ns, name, pt, data, subresources...), &v1alpha2.IPPool{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.IPPool), err +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/fake_nsx.vmware.com_client.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/fake_nsx.vmware.com_client.go new file mode 100644 index 000000000..d75ec38c6 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/fake/fake_nsx.vmware.com_client.go @@ -0,0 +1,27 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeNsxV1alpha2 struct { + *testing.Fake +} + +func (c *FakeNsxV1alpha2) IPPools(namespace string) v1alpha2.IPPoolInterface { + return &FakeIPPools{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeNsxV1alpha2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/generated_expansion.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/generated_expansion.go new file mode 100644 index 000000000..39b9a5e25 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/generated_expansion.go @@ -0,0 +1,8 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +type IPPoolExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/ippool.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/ippool.go new file mode 100644 index 000000000..230300c9a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/ippool.go @@ -0,0 +1,182 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + scheme "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// IPPoolsGetter has a method to return a IPPoolInterface. +// A group's client should implement this interface. +type IPPoolsGetter interface { + IPPools(namespace string) IPPoolInterface +} + +// IPPoolInterface has methods to work with IPPool resources. +type IPPoolInterface interface { + Create(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.CreateOptions) (*v1alpha2.IPPool, error) + Update(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.UpdateOptions) (*v1alpha2.IPPool, error) + UpdateStatus(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.UpdateOptions) (*v1alpha2.IPPool, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.IPPool, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.IPPoolList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.IPPool, err error) + IPPoolExpansion +} + +// iPPools implements IPPoolInterface +type iPPools struct { + client rest.Interface + ns string +} + +// newIPPools returns a IPPools +func newIPPools(c *NsxV1alpha2Client, namespace string) *iPPools { + return &iPPools{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the iPPool, and returns the corresponding iPPool object, and an error if there is any. +func (c *iPPools) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.IPPool, err error) { + result = &v1alpha2.IPPool{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ippools"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of IPPools that match those selectors. +func (c *iPPools) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.IPPoolList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.IPPoolList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested iPPools. +func (c *iPPools) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a iPPool and creates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *iPPools) Create(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.CreateOptions) (result *v1alpha2.IPPool, err error) { + result = &v1alpha2.IPPool{} + err = c.client.Post(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(iPPool). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a iPPool and updates it. Returns the server's representation of the iPPool, and an error, if there is any. +func (c *iPPools) Update(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.UpdateOptions) (result *v1alpha2.IPPool, err error) { + result = &v1alpha2.IPPool{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ippools"). + Name(iPPool.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(iPPool). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *iPPools) UpdateStatus(ctx context.Context, iPPool *v1alpha2.IPPool, opts v1.UpdateOptions) (result *v1alpha2.IPPool, err error) { + result = &v1alpha2.IPPool{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ippools"). + Name(iPPool.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(iPPool). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the iPPool and deletes it. Returns an error if one occurs. +func (c *iPPools) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ippools"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *iPPools) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("ippools"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched iPPool. +func (c *iPPools) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.IPPool, err error) { + result = &v1alpha2.IPPool{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("ippools"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/nsx.vmware.com_client.go b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/nsx.vmware.com_client.go new file mode 100644 index 000000000..29e558aa9 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/nsx.vmware.com/v1alpha2/nsx.vmware.com_client.go @@ -0,0 +1,94 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "net/http" + + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type NsxV1alpha2Interface interface { + RESTClient() rest.Interface + IPPoolsGetter +} + +// NsxV1alpha2Client is used to interact with features provided by the nsx.vmware.com group. +type NsxV1alpha2Client struct { + restClient rest.Interface +} + +func (c *NsxV1alpha2Client) IPPools(namespace string) IPPoolInterface { + return newIPPools(c, namespace) +} + +// NewForConfig creates a new NsxV1alpha2Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*NsxV1alpha2Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new NsxV1alpha2Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*NsxV1alpha2Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &NsxV1alpha2Client{client}, nil +} + +// NewForConfigOrDie creates a new NsxV1alpha2Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *NsxV1alpha2Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new NsxV1alpha2Client for the given RESTClient. +func New(c rest.Interface) *NsxV1alpha2Client { + return &NsxV1alpha2Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha2.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *NsxV1alpha2Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/go.mod b/pkg/client/go.mod new file mode 100644 index 000000000..85976e119 --- /dev/null +++ b/pkg/client/go.mod @@ -0,0 +1,51 @@ +module github.com/vmware-tanzu/nsx-operator/pkg/client + +go 1.19 + +require ( + github.com/vmware-tanzu/nsx-operator/pkg/apis v1.0.0 + k8s.io/apimachinery v0.28.4 + k8s.io/client-go v0.28.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.28.4 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/controller-runtime v0.14.5 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/pkg/client/go.sum b/pkg/client/go.sum new file mode 100644 index 000000000..98cf4d18e --- /dev/null +++ b/pkg/client/go.sum @@ -0,0 +1,153 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/vmware-tanzu/nsx-operator/pkg/apis v1.0.0 h1:jmHI88hySjGqkpc/QUmSY5G5SsDvoXxmKFEK/GmcHWs= +github.com/vmware-tanzu/nsx-operator/pkg/apis v1.0.0/go.mod h1:ZR/7rewflpAhnswQ6NVkFN0JmaqHgmvDyFVsJLmZ+pw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= +k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= +k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= +k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= +sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go new file mode 100644 index 000000000..1499be5ad --- /dev/null +++ b/pkg/client/informers/externalversions/factory.go @@ -0,0 +1,238 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + nsxvmwarecom "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/nsx.vmware.com" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client versioned.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.shuttingDown { + return + } + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() + f.startedInformers[informerType] = true + } + } +} + +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + + Nsx() nsxvmwarecom.Interface +} + +func (f *sharedInformerFactory) Nsx() nsxvmwarecom.Interface { + return nsxvmwarecom.New(f, f.namespace, f.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go new file mode 100644 index 000000000..441a0b73e --- /dev/null +++ b/pkg/client/informers/externalversions/generic.go @@ -0,0 +1,70 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + "fmt" + + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=nsx.vmware.com, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("ippools"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().IPPools().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("nsxserviceaccounts"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().NSXServiceAccounts().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("securitypolicies"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().SecurityPolicies().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("staticroutes"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().StaticRoutes().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("subnets"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().Subnets().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("subnetports"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().SubnetPorts().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("subnetsets"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().SubnetSets().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("vpcs"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().VPCs().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("vpcnetworkconfigurations"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha1().VPCNetworkConfigurations().Informer()}, nil + + // Group=nsx.vmware.com, Version=v1alpha2 + case v1alpha2.SchemeGroupVersion.WithResource("ippools"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Nsx().V1alpha2().IPPools().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100644 index 000000000..8ff8710c2 --- /dev/null +++ b/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,27 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package internalinterfaces + +import ( + time "time" + + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" +) + +// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/interface.go b/pkg/client/informers/externalversions/nsx.vmware.com/interface.go new file mode 100644 index 000000000..de4b44def --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/interface.go @@ -0,0 +1,41 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package nsx + +import ( + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1" + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface + // V1alpha2 provides access to shared informers for resources in V1alpha2. + V1alpha2() v1alpha2.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} + +// V1alpha2 returns a new v1alpha2.Interface. +func (g *group) V1alpha2() v1alpha2.Interface { + return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/interface.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/interface.go new file mode 100644 index 000000000..bde8e8c52 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/interface.go @@ -0,0 +1,88 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // IPPools returns a IPPoolInformer. + IPPools() IPPoolInformer + // NSXServiceAccounts returns a NSXServiceAccountInformer. + NSXServiceAccounts() NSXServiceAccountInformer + // SecurityPolicies returns a SecurityPolicyInformer. + SecurityPolicies() SecurityPolicyInformer + // StaticRoutes returns a StaticRouteInformer. + StaticRoutes() StaticRouteInformer + // Subnets returns a SubnetInformer. + Subnets() SubnetInformer + // SubnetPorts returns a SubnetPortInformer. + SubnetPorts() SubnetPortInformer + // SubnetSets returns a SubnetSetInformer. + SubnetSets() SubnetSetInformer + // VPCs returns a VPCInformer. + VPCs() VPCInformer + // VPCNetworkConfigurations returns a VPCNetworkConfigurationInformer. + VPCNetworkConfigurations() VPCNetworkConfigurationInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// IPPools returns a IPPoolInformer. +func (v *version) IPPools() IPPoolInformer { + return &iPPoolInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// NSXServiceAccounts returns a NSXServiceAccountInformer. +func (v *version) NSXServiceAccounts() NSXServiceAccountInformer { + return &nSXServiceAccountInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// SecurityPolicies returns a SecurityPolicyInformer. +func (v *version) SecurityPolicies() SecurityPolicyInformer { + return &securityPolicyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// StaticRoutes returns a StaticRouteInformer. +func (v *version) StaticRoutes() StaticRouteInformer { + return &staticRouteInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// Subnets returns a SubnetInformer. +func (v *version) Subnets() SubnetInformer { + return &subnetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// SubnetPorts returns a SubnetPortInformer. +func (v *version) SubnetPorts() SubnetPortInformer { + return &subnetPortInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// SubnetSets returns a SubnetSetInformer. +func (v *version) SubnetSets() SubnetSetInformer { + return &subnetSetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// VPCs returns a VPCInformer. +func (v *version) VPCs() VPCInformer { + return &vPCInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// VPCNetworkConfigurations returns a VPCNetworkConfigurationInformer. +func (v *version) VPCNetworkConfigurations() VPCNetworkConfigurationInformer { + return &vPCNetworkConfigurationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/ippool.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/ippool.go new file mode 100644 index 000000000..74f7f11cb --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/ippool.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// IPPoolInformer provides access to a shared informer and lister for +// IPPools. +type IPPoolInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.IPPoolLister +} + +type iPPoolInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewIPPoolInformer constructs a new informer for IPPool type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIPPoolInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredIPPoolInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredIPPoolInformer constructs a new informer for IPPool type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredIPPoolInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().IPPools(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().IPPools(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.IPPool{}, + resyncPeriod, + indexers, + ) +} + +func (f *iPPoolInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredIPPoolInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *iPPoolInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.IPPool{}, f.defaultInformer) +} + +func (f *iPPoolInformer) Lister() v1alpha1.IPPoolLister { + return v1alpha1.NewIPPoolLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/nsxserviceaccount.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/nsxserviceaccount.go new file mode 100644 index 000000000..43fe4e489 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/nsxserviceaccount.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// NSXServiceAccountInformer provides access to a shared informer and lister for +// NSXServiceAccounts. +type NSXServiceAccountInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.NSXServiceAccountLister +} + +type nSXServiceAccountInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewNSXServiceAccountInformer constructs a new informer for NSXServiceAccount type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewNSXServiceAccountInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredNSXServiceAccountInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredNSXServiceAccountInformer constructs a new informer for NSXServiceAccount type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredNSXServiceAccountInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().NSXServiceAccounts(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().NSXServiceAccounts(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.NSXServiceAccount{}, + resyncPeriod, + indexers, + ) +} + +func (f *nSXServiceAccountInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredNSXServiceAccountInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *nSXServiceAccountInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.NSXServiceAccount{}, f.defaultInformer) +} + +func (f *nSXServiceAccountInformer) Lister() v1alpha1.NSXServiceAccountLister { + return v1alpha1.NewNSXServiceAccountLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/securitypolicy.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/securitypolicy.go new file mode 100644 index 000000000..e2c6d6496 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/securitypolicy.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// SecurityPolicyInformer provides access to a shared informer and lister for +// SecurityPolicies. +type SecurityPolicyInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.SecurityPolicyLister +} + +type securityPolicyInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSecurityPolicyInformer constructs a new informer for SecurityPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSecurityPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSecurityPolicyInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSecurityPolicyInformer constructs a new informer for SecurityPolicy type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSecurityPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().SecurityPolicies(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().SecurityPolicies(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.SecurityPolicy{}, + resyncPeriod, + indexers, + ) +} + +func (f *securityPolicyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSecurityPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *securityPolicyInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.SecurityPolicy{}, f.defaultInformer) +} + +func (f *securityPolicyInformer) Lister() v1alpha1.SecurityPolicyLister { + return v1alpha1.NewSecurityPolicyLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/staticroute.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/staticroute.go new file mode 100644 index 000000000..fc16b54c0 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/staticroute.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// StaticRouteInformer provides access to a shared informer and lister for +// StaticRoutes. +type StaticRouteInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.StaticRouteLister +} + +type staticRouteInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewStaticRouteInformer constructs a new informer for StaticRoute type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStaticRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredStaticRouteInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredStaticRouteInformer constructs a new informer for StaticRoute type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredStaticRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().StaticRoutes(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().StaticRoutes(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.StaticRoute{}, + resyncPeriod, + indexers, + ) +} + +func (f *staticRouteInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredStaticRouteInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *staticRouteInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.StaticRoute{}, f.defaultInformer) +} + +func (f *staticRouteInformer) Lister() v1alpha1.StaticRouteLister { + return v1alpha1.NewStaticRouteLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnet.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnet.go new file mode 100644 index 000000000..133572393 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnet.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// SubnetInformer provides access to a shared informer and lister for +// Subnets. +type SubnetInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.SubnetLister +} + +type subnetInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSubnetInformer constructs a new informer for Subnet type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSubnetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSubnetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSubnetInformer constructs a new informer for Subnet type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSubnetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().Subnets(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().Subnets(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.Subnet{}, + resyncPeriod, + indexers, + ) +} + +func (f *subnetInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSubnetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *subnetInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.Subnet{}, f.defaultInformer) +} + +func (f *subnetInformer) Lister() v1alpha1.SubnetLister { + return v1alpha1.NewSubnetLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnetport.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnetport.go new file mode 100644 index 000000000..c681c3d6c --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnetport.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// SubnetPortInformer provides access to a shared informer and lister for +// SubnetPorts. +type SubnetPortInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.SubnetPortLister +} + +type subnetPortInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSubnetPortInformer constructs a new informer for SubnetPort type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSubnetPortInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSubnetPortInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSubnetPortInformer constructs a new informer for SubnetPort type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSubnetPortInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().SubnetPorts(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().SubnetPorts(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.SubnetPort{}, + resyncPeriod, + indexers, + ) +} + +func (f *subnetPortInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSubnetPortInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *subnetPortInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.SubnetPort{}, f.defaultInformer) +} + +func (f *subnetPortInformer) Lister() v1alpha1.SubnetPortLister { + return v1alpha1.NewSubnetPortLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnetset.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnetset.go new file mode 100644 index 000000000..c94e8f931 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/subnetset.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// SubnetSetInformer provides access to a shared informer and lister for +// SubnetSets. +type SubnetSetInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.SubnetSetLister +} + +type subnetSetInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSubnetSetInformer constructs a new informer for SubnetSet type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSubnetSetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSubnetSetInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSubnetSetInformer constructs a new informer for SubnetSet type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSubnetSetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().SubnetSets(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().SubnetSets(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.SubnetSet{}, + resyncPeriod, + indexers, + ) +} + +func (f *subnetSetInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSubnetSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *subnetSetInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.SubnetSet{}, f.defaultInformer) +} + +func (f *subnetSetInformer) Lister() v1alpha1.SubnetSetLister { + return v1alpha1.NewSubnetSetLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/vpc.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/vpc.go new file mode 100644 index 000000000..65643a0b7 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/vpc.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// VPCInformer provides access to a shared informer and lister for +// VPCs. +type VPCInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.VPCLister +} + +type vPCInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVPCInformer constructs a new informer for VPC type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVPCInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVPCInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVPCInformer constructs a new informer for VPC type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredVPCInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().VPCs(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().VPCs(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.VPC{}, + resyncPeriod, + indexers, + ) +} + +func (f *vPCInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVPCInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *vPCInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.VPC{}, f.defaultInformer) +} + +func (f *vPCInformer) Lister() v1alpha1.VPCLister { + return v1alpha1.NewVPCLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go new file mode 100644 index 000000000..332e194a3 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// VPCNetworkConfigurationInformer provides access to a shared informer and lister for +// VPCNetworkConfigurations. +type VPCNetworkConfigurationInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.VPCNetworkConfigurationLister +} + +type vPCNetworkConfigurationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVPCNetworkConfigurationInformer constructs a new informer for VPCNetworkConfiguration type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVPCNetworkConfigurationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVPCNetworkConfigurationInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVPCNetworkConfigurationInformer constructs a new informer for VPCNetworkConfiguration type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredVPCNetworkConfigurationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().VPCNetworkConfigurations(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha1().VPCNetworkConfigurations(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha1.VPCNetworkConfiguration{}, + resyncPeriod, + indexers, + ) +} + +func (f *vPCNetworkConfigurationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVPCNetworkConfigurationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *vPCNetworkConfigurationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha1.VPCNetworkConfiguration{}, f.defaultInformer) +} + +func (f *vPCNetworkConfigurationInformer) Lister() v1alpha1.VPCNetworkConfigurationLister { + return v1alpha1.NewVPCNetworkConfigurationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2/interface.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2/interface.go new file mode 100644 index 000000000..968cc1b77 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2/interface.go @@ -0,0 +1,32 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // IPPools returns a IPPoolInformer. + IPPools() IPPoolInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// IPPools returns a IPPoolInformer. +func (v *version) IPPools() IPPoolInformer { + return &iPPoolInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2/ippool.go b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2/ippool.go new file mode 100644 index 000000000..4000bff31 --- /dev/null +++ b/pkg/client/informers/externalversions/nsx.vmware.com/v1alpha2/ippool.go @@ -0,0 +1,77 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + time "time" + + nsxvmwarecomv1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + versioned "github.com/vmware-tanzu/nsx-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/vmware-tanzu/nsx-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/client/listers/nsx.vmware.com/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// IPPoolInformer provides access to a shared informer and lister for +// IPPools. +type IPPoolInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.IPPoolLister +} + +type iPPoolInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewIPPoolInformer constructs a new informer for IPPool type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIPPoolInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredIPPoolInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredIPPoolInformer constructs a new informer for IPPool type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredIPPoolInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha2().IPPools(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NsxV1alpha2().IPPools(namespace).Watch(context.TODO(), options) + }, + }, + &nsxvmwarecomv1alpha2.IPPool{}, + resyncPeriod, + indexers, + ) +} + +func (f *iPPoolInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredIPPoolInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *iPPoolInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&nsxvmwarecomv1alpha2.IPPool{}, f.defaultInformer) +} + +func (f *iPPoolInformer) Lister() v1alpha2.IPPoolLister { + return v1alpha2.NewIPPoolLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/expansion_generated.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/expansion_generated.go new file mode 100644 index 000000000..9bf8de01f --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/expansion_generated.go @@ -0,0 +1,78 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +// IPPoolListerExpansion allows custom methods to be added to +// IPPoolLister. +type IPPoolListerExpansion interface{} + +// IPPoolNamespaceListerExpansion allows custom methods to be added to +// IPPoolNamespaceLister. +type IPPoolNamespaceListerExpansion interface{} + +// NSXServiceAccountListerExpansion allows custom methods to be added to +// NSXServiceAccountLister. +type NSXServiceAccountListerExpansion interface{} + +// NSXServiceAccountNamespaceListerExpansion allows custom methods to be added to +// NSXServiceAccountNamespaceLister. +type NSXServiceAccountNamespaceListerExpansion interface{} + +// SecurityPolicyListerExpansion allows custom methods to be added to +// SecurityPolicyLister. +type SecurityPolicyListerExpansion interface{} + +// SecurityPolicyNamespaceListerExpansion allows custom methods to be added to +// SecurityPolicyNamespaceLister. +type SecurityPolicyNamespaceListerExpansion interface{} + +// StaticRouteListerExpansion allows custom methods to be added to +// StaticRouteLister. +type StaticRouteListerExpansion interface{} + +// StaticRouteNamespaceListerExpansion allows custom methods to be added to +// StaticRouteNamespaceLister. +type StaticRouteNamespaceListerExpansion interface{} + +// SubnetListerExpansion allows custom methods to be added to +// SubnetLister. +type SubnetListerExpansion interface{} + +// SubnetNamespaceListerExpansion allows custom methods to be added to +// SubnetNamespaceLister. +type SubnetNamespaceListerExpansion interface{} + +// SubnetPortListerExpansion allows custom methods to be added to +// SubnetPortLister. +type SubnetPortListerExpansion interface{} + +// SubnetPortNamespaceListerExpansion allows custom methods to be added to +// SubnetPortNamespaceLister. +type SubnetPortNamespaceListerExpansion interface{} + +// SubnetSetListerExpansion allows custom methods to be added to +// SubnetSetLister. +type SubnetSetListerExpansion interface{} + +// SubnetSetNamespaceListerExpansion allows custom methods to be added to +// SubnetSetNamespaceLister. +type SubnetSetNamespaceListerExpansion interface{} + +// VPCListerExpansion allows custom methods to be added to +// VPCLister. +type VPCListerExpansion interface{} + +// VPCNamespaceListerExpansion allows custom methods to be added to +// VPCNamespaceLister. +type VPCNamespaceListerExpansion interface{} + +// VPCNetworkConfigurationListerExpansion allows custom methods to be added to +// VPCNetworkConfigurationLister. +type VPCNetworkConfigurationListerExpansion interface{} + +// VPCNetworkConfigurationNamespaceListerExpansion allows custom methods to be added to +// VPCNetworkConfigurationNamespaceLister. +type VPCNetworkConfigurationNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/ippool.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/ippool.go new file mode 100644 index 000000000..9feb6e5e3 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/ippool.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// IPPoolLister helps list IPPools. +// All objects returned here must be treated as read-only. +type IPPoolLister interface { + // List lists all IPPools in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.IPPool, err error) + // IPPools returns an object that can list and get IPPools. + IPPools(namespace string) IPPoolNamespaceLister + IPPoolListerExpansion +} + +// iPPoolLister implements the IPPoolLister interface. +type iPPoolLister struct { + indexer cache.Indexer +} + +// NewIPPoolLister returns a new IPPoolLister. +func NewIPPoolLister(indexer cache.Indexer) IPPoolLister { + return &iPPoolLister{indexer: indexer} +} + +// List lists all IPPools in the indexer. +func (s *iPPoolLister) List(selector labels.Selector) (ret []*v1alpha1.IPPool, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.IPPool)) + }) + return ret, err +} + +// IPPools returns an object that can list and get IPPools. +func (s *iPPoolLister) IPPools(namespace string) IPPoolNamespaceLister { + return iPPoolNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// IPPoolNamespaceLister helps list and get IPPools. +// All objects returned here must be treated as read-only. +type IPPoolNamespaceLister interface { + // List lists all IPPools in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.IPPool, err error) + // Get retrieves the IPPool from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.IPPool, error) + IPPoolNamespaceListerExpansion +} + +// iPPoolNamespaceLister implements the IPPoolNamespaceLister +// interface. +type iPPoolNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all IPPools in the indexer for a given namespace. +func (s iPPoolNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.IPPool, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.IPPool)) + }) + return ret, err +} + +// Get retrieves the IPPool from the indexer for a given namespace and name. +func (s iPPoolNamespaceLister) Get(name string) (*v1alpha1.IPPool, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("ippool"), name) + } + return obj.(*v1alpha1.IPPool), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/nsxserviceaccount.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/nsxserviceaccount.go new file mode 100644 index 000000000..d84301511 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/nsxserviceaccount.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// NSXServiceAccountLister helps list NSXServiceAccounts. +// All objects returned here must be treated as read-only. +type NSXServiceAccountLister interface { + // List lists all NSXServiceAccounts in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.NSXServiceAccount, err error) + // NSXServiceAccounts returns an object that can list and get NSXServiceAccounts. + NSXServiceAccounts(namespace string) NSXServiceAccountNamespaceLister + NSXServiceAccountListerExpansion +} + +// nSXServiceAccountLister implements the NSXServiceAccountLister interface. +type nSXServiceAccountLister struct { + indexer cache.Indexer +} + +// NewNSXServiceAccountLister returns a new NSXServiceAccountLister. +func NewNSXServiceAccountLister(indexer cache.Indexer) NSXServiceAccountLister { + return &nSXServiceAccountLister{indexer: indexer} +} + +// List lists all NSXServiceAccounts in the indexer. +func (s *nSXServiceAccountLister) List(selector labels.Selector) (ret []*v1alpha1.NSXServiceAccount, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.NSXServiceAccount)) + }) + return ret, err +} + +// NSXServiceAccounts returns an object that can list and get NSXServiceAccounts. +func (s *nSXServiceAccountLister) NSXServiceAccounts(namespace string) NSXServiceAccountNamespaceLister { + return nSXServiceAccountNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// NSXServiceAccountNamespaceLister helps list and get NSXServiceAccounts. +// All objects returned here must be treated as read-only. +type NSXServiceAccountNamespaceLister interface { + // List lists all NSXServiceAccounts in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.NSXServiceAccount, err error) + // Get retrieves the NSXServiceAccount from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.NSXServiceAccount, error) + NSXServiceAccountNamespaceListerExpansion +} + +// nSXServiceAccountNamespaceLister implements the NSXServiceAccountNamespaceLister +// interface. +type nSXServiceAccountNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all NSXServiceAccounts in the indexer for a given namespace. +func (s nSXServiceAccountNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.NSXServiceAccount, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.NSXServiceAccount)) + }) + return ret, err +} + +// Get retrieves the NSXServiceAccount from the indexer for a given namespace and name. +func (s nSXServiceAccountNamespaceLister) Get(name string) (*v1alpha1.NSXServiceAccount, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("nsxserviceaccount"), name) + } + return obj.(*v1alpha1.NSXServiceAccount), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/securitypolicy.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/securitypolicy.go new file mode 100644 index 000000000..d5b0b0ca8 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/securitypolicy.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// SecurityPolicyLister helps list SecurityPolicies. +// All objects returned here must be treated as read-only. +type SecurityPolicyLister interface { + // List lists all SecurityPolicies in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SecurityPolicy, err error) + // SecurityPolicies returns an object that can list and get SecurityPolicies. + SecurityPolicies(namespace string) SecurityPolicyNamespaceLister + SecurityPolicyListerExpansion +} + +// securityPolicyLister implements the SecurityPolicyLister interface. +type securityPolicyLister struct { + indexer cache.Indexer +} + +// NewSecurityPolicyLister returns a new SecurityPolicyLister. +func NewSecurityPolicyLister(indexer cache.Indexer) SecurityPolicyLister { + return &securityPolicyLister{indexer: indexer} +} + +// List lists all SecurityPolicies in the indexer. +func (s *securityPolicyLister) List(selector labels.Selector) (ret []*v1alpha1.SecurityPolicy, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SecurityPolicy)) + }) + return ret, err +} + +// SecurityPolicies returns an object that can list and get SecurityPolicies. +func (s *securityPolicyLister) SecurityPolicies(namespace string) SecurityPolicyNamespaceLister { + return securityPolicyNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SecurityPolicyNamespaceLister helps list and get SecurityPolicies. +// All objects returned here must be treated as read-only. +type SecurityPolicyNamespaceLister interface { + // List lists all SecurityPolicies in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SecurityPolicy, err error) + // Get retrieves the SecurityPolicy from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.SecurityPolicy, error) + SecurityPolicyNamespaceListerExpansion +} + +// securityPolicyNamespaceLister implements the SecurityPolicyNamespaceLister +// interface. +type securityPolicyNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SecurityPolicies in the indexer for a given namespace. +func (s securityPolicyNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.SecurityPolicy, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SecurityPolicy)) + }) + return ret, err +} + +// Get retrieves the SecurityPolicy from the indexer for a given namespace and name. +func (s securityPolicyNamespaceLister) Get(name string) (*v1alpha1.SecurityPolicy, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("securitypolicy"), name) + } + return obj.(*v1alpha1.SecurityPolicy), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/staticroute.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/staticroute.go new file mode 100644 index 000000000..3f060010c --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/staticroute.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// StaticRouteLister helps list StaticRoutes. +// All objects returned here must be treated as read-only. +type StaticRouteLister interface { + // List lists all StaticRoutes in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.StaticRoute, err error) + // StaticRoutes returns an object that can list and get StaticRoutes. + StaticRoutes(namespace string) StaticRouteNamespaceLister + StaticRouteListerExpansion +} + +// staticRouteLister implements the StaticRouteLister interface. +type staticRouteLister struct { + indexer cache.Indexer +} + +// NewStaticRouteLister returns a new StaticRouteLister. +func NewStaticRouteLister(indexer cache.Indexer) StaticRouteLister { + return &staticRouteLister{indexer: indexer} +} + +// List lists all StaticRoutes in the indexer. +func (s *staticRouteLister) List(selector labels.Selector) (ret []*v1alpha1.StaticRoute, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.StaticRoute)) + }) + return ret, err +} + +// StaticRoutes returns an object that can list and get StaticRoutes. +func (s *staticRouteLister) StaticRoutes(namespace string) StaticRouteNamespaceLister { + return staticRouteNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// StaticRouteNamespaceLister helps list and get StaticRoutes. +// All objects returned here must be treated as read-only. +type StaticRouteNamespaceLister interface { + // List lists all StaticRoutes in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.StaticRoute, err error) + // Get retrieves the StaticRoute from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.StaticRoute, error) + StaticRouteNamespaceListerExpansion +} + +// staticRouteNamespaceLister implements the StaticRouteNamespaceLister +// interface. +type staticRouteNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all StaticRoutes in the indexer for a given namespace. +func (s staticRouteNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.StaticRoute, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.StaticRoute)) + }) + return ret, err +} + +// Get retrieves the StaticRoute from the indexer for a given namespace and name. +func (s staticRouteNamespaceLister) Get(name string) (*v1alpha1.StaticRoute, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("staticroute"), name) + } + return obj.(*v1alpha1.StaticRoute), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/subnet.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/subnet.go new file mode 100644 index 000000000..0b8cd736b --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/subnet.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// SubnetLister helps list Subnets. +// All objects returned here must be treated as read-only. +type SubnetLister interface { + // List lists all Subnets in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.Subnet, err error) + // Subnets returns an object that can list and get Subnets. + Subnets(namespace string) SubnetNamespaceLister + SubnetListerExpansion +} + +// subnetLister implements the SubnetLister interface. +type subnetLister struct { + indexer cache.Indexer +} + +// NewSubnetLister returns a new SubnetLister. +func NewSubnetLister(indexer cache.Indexer) SubnetLister { + return &subnetLister{indexer: indexer} +} + +// List lists all Subnets in the indexer. +func (s *subnetLister) List(selector labels.Selector) (ret []*v1alpha1.Subnet, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Subnet)) + }) + return ret, err +} + +// Subnets returns an object that can list and get Subnets. +func (s *subnetLister) Subnets(namespace string) SubnetNamespaceLister { + return subnetNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SubnetNamespaceLister helps list and get Subnets. +// All objects returned here must be treated as read-only. +type SubnetNamespaceLister interface { + // List lists all Subnets in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.Subnet, err error) + // Get retrieves the Subnet from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.Subnet, error) + SubnetNamespaceListerExpansion +} + +// subnetNamespaceLister implements the SubnetNamespaceLister +// interface. +type subnetNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Subnets in the indexer for a given namespace. +func (s subnetNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Subnet, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Subnet)) + }) + return ret, err +} + +// Get retrieves the Subnet from the indexer for a given namespace and name. +func (s subnetNamespaceLister) Get(name string) (*v1alpha1.Subnet, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("subnet"), name) + } + return obj.(*v1alpha1.Subnet), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/subnetport.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/subnetport.go new file mode 100644 index 000000000..7f1b170aa --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/subnetport.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// SubnetPortLister helps list SubnetPorts. +// All objects returned here must be treated as read-only. +type SubnetPortLister interface { + // List lists all SubnetPorts in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SubnetPort, err error) + // SubnetPorts returns an object that can list and get SubnetPorts. + SubnetPorts(namespace string) SubnetPortNamespaceLister + SubnetPortListerExpansion +} + +// subnetPortLister implements the SubnetPortLister interface. +type subnetPortLister struct { + indexer cache.Indexer +} + +// NewSubnetPortLister returns a new SubnetPortLister. +func NewSubnetPortLister(indexer cache.Indexer) SubnetPortLister { + return &subnetPortLister{indexer: indexer} +} + +// List lists all SubnetPorts in the indexer. +func (s *subnetPortLister) List(selector labels.Selector) (ret []*v1alpha1.SubnetPort, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SubnetPort)) + }) + return ret, err +} + +// SubnetPorts returns an object that can list and get SubnetPorts. +func (s *subnetPortLister) SubnetPorts(namespace string) SubnetPortNamespaceLister { + return subnetPortNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SubnetPortNamespaceLister helps list and get SubnetPorts. +// All objects returned here must be treated as read-only. +type SubnetPortNamespaceLister interface { + // List lists all SubnetPorts in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SubnetPort, err error) + // Get retrieves the SubnetPort from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.SubnetPort, error) + SubnetPortNamespaceListerExpansion +} + +// subnetPortNamespaceLister implements the SubnetPortNamespaceLister +// interface. +type subnetPortNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SubnetPorts in the indexer for a given namespace. +func (s subnetPortNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.SubnetPort, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SubnetPort)) + }) + return ret, err +} + +// Get retrieves the SubnetPort from the indexer for a given namespace and name. +func (s subnetPortNamespaceLister) Get(name string) (*v1alpha1.SubnetPort, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("subnetport"), name) + } + return obj.(*v1alpha1.SubnetPort), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/subnetset.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/subnetset.go new file mode 100644 index 000000000..9898f6097 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/subnetset.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// SubnetSetLister helps list SubnetSets. +// All objects returned here must be treated as read-only. +type SubnetSetLister interface { + // List lists all SubnetSets in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SubnetSet, err error) + // SubnetSets returns an object that can list and get SubnetSets. + SubnetSets(namespace string) SubnetSetNamespaceLister + SubnetSetListerExpansion +} + +// subnetSetLister implements the SubnetSetLister interface. +type subnetSetLister struct { + indexer cache.Indexer +} + +// NewSubnetSetLister returns a new SubnetSetLister. +func NewSubnetSetLister(indexer cache.Indexer) SubnetSetLister { + return &subnetSetLister{indexer: indexer} +} + +// List lists all SubnetSets in the indexer. +func (s *subnetSetLister) List(selector labels.Selector) (ret []*v1alpha1.SubnetSet, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SubnetSet)) + }) + return ret, err +} + +// SubnetSets returns an object that can list and get SubnetSets. +func (s *subnetSetLister) SubnetSets(namespace string) SubnetSetNamespaceLister { + return subnetSetNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SubnetSetNamespaceLister helps list and get SubnetSets. +// All objects returned here must be treated as read-only. +type SubnetSetNamespaceLister interface { + // List lists all SubnetSets in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SubnetSet, err error) + // Get retrieves the SubnetSet from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.SubnetSet, error) + SubnetSetNamespaceListerExpansion +} + +// subnetSetNamespaceLister implements the SubnetSetNamespaceLister +// interface. +type subnetSetNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SubnetSets in the indexer for a given namespace. +func (s subnetSetNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.SubnetSet, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SubnetSet)) + }) + return ret, err +} + +// Get retrieves the SubnetSet from the indexer for a given namespace and name. +func (s subnetSetNamespaceLister) Get(name string) (*v1alpha1.SubnetSet, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("subnetset"), name) + } + return obj.(*v1alpha1.SubnetSet), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/vpc.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/vpc.go new file mode 100644 index 000000000..a64fef879 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/vpc.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// VPCLister helps list VPCs. +// All objects returned here must be treated as read-only. +type VPCLister interface { + // List lists all VPCs in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.VPC, err error) + // VPCs returns an object that can list and get VPCs. + VPCs(namespace string) VPCNamespaceLister + VPCListerExpansion +} + +// vPCLister implements the VPCLister interface. +type vPCLister struct { + indexer cache.Indexer +} + +// NewVPCLister returns a new VPCLister. +func NewVPCLister(indexer cache.Indexer) VPCLister { + return &vPCLister{indexer: indexer} +} + +// List lists all VPCs in the indexer. +func (s *vPCLister) List(selector labels.Selector) (ret []*v1alpha1.VPC, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.VPC)) + }) + return ret, err +} + +// VPCs returns an object that can list and get VPCs. +func (s *vPCLister) VPCs(namespace string) VPCNamespaceLister { + return vPCNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// VPCNamespaceLister helps list and get VPCs. +// All objects returned here must be treated as read-only. +type VPCNamespaceLister interface { + // List lists all VPCs in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.VPC, err error) + // Get retrieves the VPC from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.VPC, error) + VPCNamespaceListerExpansion +} + +// vPCNamespaceLister implements the VPCNamespaceLister +// interface. +type vPCNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all VPCs in the indexer for a given namespace. +func (s vPCNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.VPC, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.VPC)) + }) + return ret, err +} + +// Get retrieves the VPC from the indexer for a given namespace and name. +func (s vPCNamespaceLister) Get(name string) (*v1alpha1.VPC, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("vpc"), name) + } + return obj.(*v1alpha1.VPC), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go b/pkg/client/listers/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go new file mode 100644 index 000000000..da7c18107 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha1/vpcnetworkconfiguration.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// VPCNetworkConfigurationLister helps list VPCNetworkConfigurations. +// All objects returned here must be treated as read-only. +type VPCNetworkConfigurationLister interface { + // List lists all VPCNetworkConfigurations in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.VPCNetworkConfiguration, err error) + // VPCNetworkConfigurations returns an object that can list and get VPCNetworkConfigurations. + VPCNetworkConfigurations(namespace string) VPCNetworkConfigurationNamespaceLister + VPCNetworkConfigurationListerExpansion +} + +// vPCNetworkConfigurationLister implements the VPCNetworkConfigurationLister interface. +type vPCNetworkConfigurationLister struct { + indexer cache.Indexer +} + +// NewVPCNetworkConfigurationLister returns a new VPCNetworkConfigurationLister. +func NewVPCNetworkConfigurationLister(indexer cache.Indexer) VPCNetworkConfigurationLister { + return &vPCNetworkConfigurationLister{indexer: indexer} +} + +// List lists all VPCNetworkConfigurations in the indexer. +func (s *vPCNetworkConfigurationLister) List(selector labels.Selector) (ret []*v1alpha1.VPCNetworkConfiguration, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.VPCNetworkConfiguration)) + }) + return ret, err +} + +// VPCNetworkConfigurations returns an object that can list and get VPCNetworkConfigurations. +func (s *vPCNetworkConfigurationLister) VPCNetworkConfigurations(namespace string) VPCNetworkConfigurationNamespaceLister { + return vPCNetworkConfigurationNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// VPCNetworkConfigurationNamespaceLister helps list and get VPCNetworkConfigurations. +// All objects returned here must be treated as read-only. +type VPCNetworkConfigurationNamespaceLister interface { + // List lists all VPCNetworkConfigurations in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.VPCNetworkConfiguration, err error) + // Get retrieves the VPCNetworkConfiguration from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.VPCNetworkConfiguration, error) + VPCNetworkConfigurationNamespaceListerExpansion +} + +// vPCNetworkConfigurationNamespaceLister implements the VPCNetworkConfigurationNamespaceLister +// interface. +type vPCNetworkConfigurationNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all VPCNetworkConfigurations in the indexer for a given namespace. +func (s vPCNetworkConfigurationNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.VPCNetworkConfiguration, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.VPCNetworkConfiguration)) + }) + return ret, err +} + +// Get retrieves the VPCNetworkConfiguration from the indexer for a given namespace and name. +func (s vPCNetworkConfigurationNamespaceLister) Get(name string) (*v1alpha1.VPCNetworkConfiguration, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("vpcnetworkconfiguration"), name) + } + return obj.(*v1alpha1.VPCNetworkConfiguration), nil +} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha2/expansion_generated.go b/pkg/client/listers/nsx.vmware.com/v1alpha2/expansion_generated.go new file mode 100644 index 000000000..eeb66bfd9 --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha2/expansion_generated.go @@ -0,0 +1,14 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +// IPPoolListerExpansion allows custom methods to be added to +// IPPoolLister. +type IPPoolListerExpansion interface{} + +// IPPoolNamespaceListerExpansion allows custom methods to be added to +// IPPoolNamespaceLister. +type IPPoolNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/nsx.vmware.com/v1alpha2/ippool.go b/pkg/client/listers/nsx.vmware.com/v1alpha2/ippool.go new file mode 100644 index 000000000..434a3986f --- /dev/null +++ b/pkg/client/listers/nsx.vmware.com/v1alpha2/ippool.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/vmware-tanzu/nsx-operator/pkg/apis/nsx.vmware.com/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// IPPoolLister helps list IPPools. +// All objects returned here must be treated as read-only. +type IPPoolLister interface { + // List lists all IPPools in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.IPPool, err error) + // IPPools returns an object that can list and get IPPools. + IPPools(namespace string) IPPoolNamespaceLister + IPPoolListerExpansion +} + +// iPPoolLister implements the IPPoolLister interface. +type iPPoolLister struct { + indexer cache.Indexer +} + +// NewIPPoolLister returns a new IPPoolLister. +func NewIPPoolLister(indexer cache.Indexer) IPPoolLister { + return &iPPoolLister{indexer: indexer} +} + +// List lists all IPPools in the indexer. +func (s *iPPoolLister) List(selector labels.Selector) (ret []*v1alpha2.IPPool, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.IPPool)) + }) + return ret, err +} + +// IPPools returns an object that can list and get IPPools. +func (s *iPPoolLister) IPPools(namespace string) IPPoolNamespaceLister { + return iPPoolNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// IPPoolNamespaceLister helps list and get IPPools. +// All objects returned here must be treated as read-only. +type IPPoolNamespaceLister interface { + // List lists all IPPools in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.IPPool, err error) + // Get retrieves the IPPool from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.IPPool, error) + IPPoolNamespaceListerExpansion +} + +// iPPoolNamespaceLister implements the IPPoolNamespaceLister +// interface. +type iPPoolNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all IPPools in the indexer for a given namespace. +func (s iPPoolNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.IPPool, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.IPPool)) + }) + return ret, err +} + +// Get retrieves the IPPool from the indexer for a given namespace and name. +func (s iPPoolNamespaceLister) Get(name string) (*v1alpha2.IPPool, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("ippool"), name) + } + return obj.(*v1alpha2.IPPool), nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go index eabc48111..97e033379 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -81,7 +81,8 @@ type DefaultConfig struct { } type CoeConfig struct { - Cluster string `ini:"cluster"` + Cluster string `ini:"cluster"` + EnableVPCNetwork bool `ini:"enable_vpc_network"` } type NsxConfig struct { @@ -95,6 +96,10 @@ type NsxConfig struct { Insecure bool `ini:"insecure"` SingleTierSrTopology bool `ini:"single_tier_sr_topology"` EnforcementPoint string `ini:"enforcement_point"` + DefaultProject string `ini:"default_project"` + ExternalIPv4Blocks []string `ini:"external_ipv4_blocks"` + DefaultSubnetSize int `ini:"default_subnet_size"` + DefaultTimeout int `ini:"default_timeout"` } type K8sConfig struct { @@ -198,9 +203,7 @@ func NewNSXOperatorConfigFromFile() (*NSXOperatorConfig, error) { func NewNSXOpertorConfig() *NSXOperatorConfig { defaultNSXOperatorConfig := &NSXOperatorConfig{ &DefaultConfig{}, - &CoeConfig{ - "", - }, + &CoeConfig{}, &NsxConfig{}, &K8sConfig{}, &VCConfig{}, @@ -214,7 +217,7 @@ func (operatorConfig *NSXOperatorConfig) validate() error { if err := operatorConfig.CoeConfig.validate(); err != nil { return err } - if err := operatorConfig.NsxConfig.validate(); err != nil { + if err := operatorConfig.NsxConfig.validate(operatorConfig.CoeConfig.EnableVPCNetwork); err != nil { return err } // TODO, verify if user&pwd, cert, jwt has any of them provided @@ -239,7 +242,7 @@ func (operatorConfig *NSXOperatorConfig) createTokenProvider() error { } else { vcCaCert, err = os.ReadFile(vcHostCACertPath) } - // If operatorConfig.VCInsecure is false, tls will the CA to verify the server + // If operatorConfig.VCInsecure is false, tls will use the CA to verify the server // certificate. If loading CA failed, tls will use the CA on local host if err != nil { configLog.Info("fail to load CA cert from file", "error", err) @@ -301,8 +304,8 @@ func (nsxConfig *NsxConfig) validateCert() error { caCount := len(nsxConfig.CaFile) // ca file has high priority than thumbprint // ca file(thumbprint) == 1 or equal to manager count - if caCount == 0 && tpCount == 0 { - err := errors.New("no ca file or thumbprint provided") + if caCount == 0 && tpCount == 0 && nsxConfig.NsxApiUser == "" && nsxConfig.NsxApiPassword == "" { + err := errors.New("no ca file or thumbprint or nsx username/password provided") configLog.Error(err, "validate NsxConfig failed") return err } @@ -331,7 +334,7 @@ func (nsxConfig *NsxConfig) validateCert() error { return nil } -func (nsxConfig *NsxConfig) validate() error { +func (nsxConfig *NsxConfig) validate(enableVPC bool) error { nsxConfig.NsxApiManagers = removeEmptyItem(nsxConfig.NsxApiManagers) mCount := len(nsxConfig.NsxApiManagers) if mCount == 0 { @@ -353,3 +356,7 @@ func (coeConfig *CoeConfig) validate() error { } return nil } + +func (nsxConfig *NsxConfig) ValidateConfigFromCmd() error { + return nsxConfig.validate(true) +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 3ec95733e..d81de4551 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -59,40 +59,35 @@ func TestConfig_CoeConfig(t *testing.T) { func TestConfig_NsxConfig(t *testing.T) { nsxConfig := &NsxConfig{} expect := errors.New("invalid field " + "NsxApiManagers") - err := nsxConfig.validate() + err := nsxConfig.validate(false) assert.Equal(t, err, expect) nsxConfig.NsxApiManagers = []string{"10.0.0.1"} - expect = errors.New("no ca file or thumbprint provided") - err = nsxConfig.validate() + expect = errors.New("no ca file or thumbprint or nsx username/password provided") + err = nsxConfig.validate(false) assert.Equal(t, err, expect) nsxConfig.Thumbprint = []string{"0a:fc"} - err = nsxConfig.validate() + err = nsxConfig.validate(false) assert.Equal(t, err, nil) nsxConfig.CaFile = []string{"0a:fc", "ob:fd"} expect = errors.New("ca file count not match manager count") - err = nsxConfig.validate() + err = nsxConfig.validate(false) assert.Equal(t, err, expect) // Insecure == true nsxConfig.CaFile = []string{"0a:fc", "ob:fd"} nsxConfig.Insecure = true - err = nsxConfig.validate() + err = nsxConfig.validate(false) assert.Equal(t, err, nil) nsxConfig.CaFile = []string{} nsxConfig.Insecure = false nsxConfig.Thumbprint = []string{"0a:fc", "ob:fd"} expect = errors.New("thumbprint count not match manager count") - err = nsxConfig.validate() + err = nsxConfig.validate(false) assert.Equal(t, err, expect) - - nsxConfig.NsxApiManagers = []string{"10.0.0.1", "", ""} - err = nsxConfig.validate() - assert.Equal(t, err, expect) - } func TestConfig_NewNSXOperatorConfigFromFile(t *testing.T) { diff --git a/pkg/controllers/common/types.go b/pkg/controllers/common/types.go index 5009c8fc8..b280edace 100644 --- a/pkg/controllers/common/types.go +++ b/pkg/controllers/common/types.go @@ -10,12 +10,27 @@ import ( const ( MetricResTypeSecurityPolicy = "securitypolicy" + MetricResTypeIPPool = "ippool" MetricResTypeNSXServiceAccount = "nsxserviceaccount" + MetricResTypeSubnetPort = "subnetport" + MetricResTypeStaticRoute = "staticroute" + MetricResTypeSubnet = "subnet" + MetricResTypeSubnetSet = "subnetset" + MetricResTypeVPC = "vpc" + MetricResTypeNamespace = "namespace" + MetricResTypePod = "pod" + MetricResTypeNode = "node" + + LabelK8sMasterRole = "node-role.kubernetes.io/master" + LabelK8sControlRole = "node-role.kubernetes.io/control-plane" ) var ( - ResultNormal = ctrl.Result{} - ResultRequeue = ctrl.Result{Requeue: true} + ResultNormal = ctrl.Result{} + ResultRequeue = ctrl.Result{Requeue: true} + // for k8s events that need to retry in short loop, eg: namespace creation + ResultRequeueAfter10sec = ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second} + // for unstable event, eg: failed to k8s resources when reconciling, may due to k8s unstable ResultRequeueAfter5mins = ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Minute} ServiceMediator = mediator.ServiceMediator{} diff --git a/pkg/controllers/common/utils.go b/pkg/controllers/common/utils.go new file mode 100644 index 000000000..e9ffe1453 --- /dev/null +++ b/pkg/controllers/common/utils.go @@ -0,0 +1,108 @@ +package common + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +var ( + log = logger.Log + lock = &sync.Mutex{} +) + +func AllocateSubnetFromSubnetSet(subnetSet *v1alpha1.SubnetSet) (string, error) { + // TODO: For now, this is a global lock. In the future, we need to narrow its scope down to improve the performance. + lock.Lock() + defer lock.Unlock() + subnetPath, err := ServiceMediator.GetAvailableSubnet(subnetSet) + if err != nil { + log.Error(err, "failed to allocate Subnet") + return "", err + } + return subnetPath, nil +} + +func getSharedNamespaceAndVpcForNamespace(client k8sclient.Client, ctx context.Context, namespaceName string) (string, string, error) { + namespace := &v1.Namespace{} + namespacedName := types.NamespacedName{Name: namespaceName} + if err := client.Get(ctx, namespacedName, namespace); err != nil { + log.Error(err, "failed to get target namespace during getting VPC for namespace") + return "", "", err + } + vpcAnnotation, exists := namespace.Annotations[servicecommon.AnnotationVPCName] + if !exists { + return "", "", nil + } + array := strings.Split(vpcAnnotation, "/") + if len(array) != 2 { + err := fmt.Errorf("invalid annotation value of '%s': %s", servicecommon.AnnotationVPCName, vpcAnnotation) + return "", "", err + } + sharedNamespaceName, sharedVpcName := array[0], array[1] + log.Info("got shared VPC for namespace", "current namespace", namespaceName, "shared VPC", sharedVpcName, "shared namespace", sharedNamespaceName) + return sharedNamespaceName, sharedVpcName, nil +} + +func GetDefaultSubnetSet(client k8sclient.Client, ctx context.Context, namespace string, resourceType string) (*v1alpha1.SubnetSet, error) { + targetNamespace, _, err := getSharedNamespaceAndVpcForNamespace(client, ctx, namespace) + if err != nil { + return nil, err + } + if targetNamespace == "" { + log.Info("namespace doesn't have shared VPC, searching the default subnetset in the current namespace", "namespace", namespace) + targetNamespace = namespace + } + subnetSet, err := getDefaultSubnetSetByNamespace(client, ctx, targetNamespace, resourceType) + if err != nil { + return nil, err + } + return subnetSet, err +} + +func getDefaultSubnetSetByNamespace(client k8sclient.Client, ctx context.Context, namespace string, resourceType string) (*v1alpha1.SubnetSet, error) { + subnetSetList := &v1alpha1.SubnetSetList{} + subnetSetSelector := &metav1.LabelSelector{ + MatchLabels: map[string]string{ + servicecommon.LabelDefaultSubnetSet: resourceType, + }, + } + labelSelector, _ := metav1.LabelSelectorAsSelector(subnetSetSelector) + opts := &k8sclient.ListOptions{ + LabelSelector: labelSelector, + Namespace: namespace, + } + if err := client.List(context.Background(), subnetSetList, opts); err != nil { + log.Error(err, "failed to list default subnetset CR", "namespace", namespace) + return nil, err + } + if len(subnetSetList.Items) == 0 { + return nil, errors.New("default subnetset not found") + } else if len(subnetSetList.Items) > 1 { + return nil, errors.New("multiple default subnetsets found") + } + subnetSet := subnetSetList.Items[0] + log.Info("got default subnetset", "subnetset.Name", subnetSet.Name, "subnetset.uid", subnetSet.UID) + return &subnetSet, nil + +} + +func NodeIsMaster(node *v1.Node) bool { + for k := range node.Labels { + if k == LabelK8sMasterRole || k == LabelK8sControlRole { + return true + } + } + return false +} diff --git a/pkg/controllers/ippool/ippool_controller.go b/pkg/controllers/ippool/ippool_controller.go new file mode 100644 index 000000000..35d131c58 --- /dev/null +++ b/pkg/controllers/ippool/ippool_controller.go @@ -0,0 +1,298 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package ippool + +import ( + "context" + "fmt" + "regexp" + "runtime" + "time" + + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/ippool" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/util" + util2 "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + log = logger.Log + resultNormal = common.ResultNormal + resultRequeue = common.ResultRequeue + MetricResType = common.MetricResTypeIPPool +) + +// IPPoolReconciler reconciles a IPPool object +type IPPoolReconciler struct { + client.Client + Scheme *apimachineryruntime.Scheme + Service *ippool.IPPoolService +} + +func deleteSuccess(r *IPPoolReconciler, _ *context.Context, _ *v1alpha2.IPPool) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResType) +} + +func deleteFail(r *IPPoolReconciler, c *context.Context, o *v1alpha2.IPPool, e *error) { + r.setReadyStatusFalse(c, o, e) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResType) +} + +func updateSuccess(r *IPPoolReconciler, c *context.Context, o *v1alpha2.IPPool) { + r.setReadyStatusTrue(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResType) +} + +func updateFail(r *IPPoolReconciler, c *context.Context, o *v1alpha2.IPPool, e *error) { + r.setReadyStatusFalse(c, o, e) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResType) +} + +func (r *IPPoolReconciler) setReadyStatusFalse(ctx *context.Context, ippool *v1alpha2.IPPool, err *error) { + conditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX IPPool could not be created/updated/deleted", + Reason: fmt.Sprintf( + "error occurred while processing the IPPool CR. Error: %v", + *err, + ), + }, + } + ippool.Status.Conditions = conditions + if ippool.Status.Subnets == nil { + ippool.Status.Subnets = make([]v1alpha2.SubnetResult, 0) + } + e := r.Client.Status().Update(*ctx, ippool) + if e != nil { + log.Error(e, "unable to update IPPool status", "ippool", ippool) + } +} + +func (r *IPPoolReconciler) setReadyStatusTrue(ctx *context.Context, ippool *v1alpha2.IPPool) { + conditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX IPPool has been successfully created/updated", + Reason: "", + }, + } + ippool.Status.Conditions = conditions + e := r.Client.Status().Update(*ctx, ippool) + if e != nil { + log.Error(e, "unable to update IPPool status", "ippool", ippool) + } +} + +func (r *IPPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + obj := &v1alpha2.IPPool{} + log.Info("reconciling ippool CR", "ippool", req.NamespacedName) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResType) + if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil { + log.Error(err, "unable to fetch ippool CR", "req", req.NamespacedName) + return resultNormal, client.IgnoreNotFound(err) + } + + // TODO: As we do not have base controller in Go, we need to take care of NSX exceptions in each controller separately. + //I agree we should not do infinite retry for all errors, but it's ok to add error handling in a following patch + + // TODO: Since only the cloud provider creates it, we can take all the validation logic into consideration later. + + // TODO: add webhook to disallow user update prefixLength + + // TODO: Tao's suggestions: Should we consider some Orphan subnets may exist? + + // TODO: Xiaopei's suggestions: is there possibility that IPPool was deleted from nsx store but NSX block subnet was not deleted? + + if obj.Spec.Type == "" { + vpcNetworkConfig := common.ServiceMediator.GetVPCNetworkConfigByNamespace(obj.Namespace) + if vpcNetworkConfig == nil { + err := fmt.Errorf("operate failed: cannot get configuration for IPPool CR") + log.Error(err, "failed to find VPCNetworkConfig for IPPool CR", "ippool", req.NamespacedName, "namespace %s", obj.Namespace) + updateFail(r, &ctx, obj, &err) + return resultRequeue, err + } + obj.Spec.Type = vpcNetworkConfig.DefaultSubnetAccessMode + } + + if obj.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResType) + if !controllerutil.ContainsFinalizer(obj, servicecommon.IPPoolFinalizerName) { + controllerutil.AddFinalizer(obj, servicecommon.IPPoolFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "add finalizer", "ippool", req.NamespacedName) + updateFail(r, &ctx, obj, &err) + return resultRequeue, err + } + log.V(1).Info("added finalizer on ippool CR", "ippool", req.NamespacedName) + } + + subnetCidrUpdated, ipPoolSubnetsUpdated, err := r.Service.CreateOrUpdateIPPool(obj) + // check if ipblock is exhausted + apiErr, _ := util.DumpAPIError(err) + if apiErr != nil { + for _, apiErrItem := range apiErr.RelatedErrors { + // 520012=IpAddressBlock with max size does not have spare capacity to satisfy new block subnet of size + if *apiErrItem.ErrorCode == 520012 { + pathPattern := `path=\[([^\]]+)\]` + pathRegex := regexp.MustCompile(pathPattern) + pathMatch := pathRegex.FindStringSubmatch(*apiErrItem.ErrorMessage) + if len(pathMatch) > 1 { + path := pathMatch[1] + if !util2.Contains(r.Service.ExhaustedIPBlock, path) { + r.Service.ExhaustedIPBlock = append(r.Service.ExhaustedIPBlock, path) + log.Info("ExhaustedIPBlock: ", "ExhaustedIPBlock", r.Service.ExhaustedIPBlock) + } + } + } + } + } + + if err != nil { + updateFail(r, &ctx, obj, &err) + // if all ip blocks are exhausted, we should not retry + if errors.As(err, &util.IPBlockAllExhaustedError{}) { + log.Error(err, "ip blocks are all exhausted, would retry exponentially", "ippool", req.NamespacedName) + r.Service.ExhaustedIPBlock = []string{} + log.Info("Clear ExhaustedIPBlock: ", "ExhaustedIPBlock", r.Service.ExhaustedIPBlock) + return common.ResultRequeueAfter10sec, err + } + log.Error(err, "operate failed, would retry exponentially", "ippool", req.NamespacedName) + return resultRequeue, err + } + if !r.Service.FullyRealized(obj) { + if len(obj.Spec.Subnets) == 0 { + updateSuccess(r, &ctx, obj) + return resultNormal, nil + } + if subnetCidrUpdated || ipPoolSubnetsUpdated { + err := fmt.Errorf("partial subnets are unrealized, would retry exponentially") + updateFail(r, &ctx, obj, &err) + log.Info("successfully reconcile ippool CR, but put back ippool again, since partial subnets are unrealized", "subnets", + r.Service.GetUnrealizedSubnetNames(obj)) + return resultRequeue, nil + } + } else { + if subnetCidrUpdated || ipPoolSubnetsUpdated || len(obj.Spec.Subnets) == 0 { + updateSuccess(r, &ctx, obj) + log.Info("successfully reconcile ippool CR and all subnets are fully realized", "ippool", obj) + } else { + log.Info("full realized already, and resources are not changed, skip updating them", "obj", obj) + } + } + } else { + if controllerutil.ContainsFinalizer(obj, servicecommon.IPPoolFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) + if err := r.Service.DeleteIPPool(obj); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "ippool", req.NamespacedName) + deleteFail(r, &ctx, obj, &err) + return resultRequeue, err + } + controllerutil.RemoveFinalizer(obj, servicecommon.IPPoolFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "ippool", req.NamespacedName) + deleteFail(r, &ctx, obj, &err) + return resultRequeue, err + } + log.V(1).Info("removed finalizer on ippool CR", "ippool", req.NamespacedName) + deleteSuccess(r, &ctx, obj) + log.Info("successfully deleted ippool CR and all subnets", "ippool", obj) + } else { + // only print a message because it's not a normal case + log.Info("ippool CR is being deleted but its finalizers cannot be recognized", "ippool", req.NamespacedName) + } + } + return resultNormal, nil +} + +func (r *IPPoolReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha2.IPPool{}). + WithEventFilter(predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + // Ignore updates to CR status in which case metadata.Generation does not change + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Suppress Delete events to avoid filtering them out in the Reconcile function + return false + }, + }). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Complete(r) +} + +// Start setup manager and launch GC +func (r *IPPoolReconciler) Start(mgr ctrl.Manager) error { + err := r.SetupWithManager(mgr) + if err != nil { + return err + } + go r.IPPoolGarbageCollector(make(chan bool), servicecommon.GCInterval) + return nil +} + +// IPPoolGarbageCollector collect ippool which has been removed from crd. +// cancel is used to break the loop during UT +func (r *IPPoolReconciler) IPPoolGarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("ippool garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxIPPoolSet := r.Service.ListIPPoolID() + if len(nsxIPPoolSet) == 0 { + continue + } + ipPoolList := &v1alpha2.IPPoolList{} + err := r.Client.List(ctx, ipPoolList) + if err != nil { + log.Error(err, "failed to list ip pool CR") + continue + } + + CRIPPoolSet := sets.NewString() + for _, ipp := range ipPoolList.Items { + CRIPPoolSet.Insert(string(ipp.UID)) + } + + log.V(2).Info("ippool garbage collector", "nsxIPPoolSet", nsxIPPoolSet, "CRIPPoolSet", CRIPPoolSet) + + for elem := range nsxIPPoolSet { + if CRIPPoolSet.Has(elem) { + continue + } + log.Info("GC collected ip pool CR", "UID", elem) + err = r.Service.DeleteIPPool(types.UID(elem)) + if err != nil { + log.Error(err, "failed to delete ip pool CR", "UID", elem) + } + } + } +} diff --git a/pkg/controllers/ippool/ippool_controller_test.go b/pkg/controllers/ippool/ippool_controller_test.go new file mode 100644 index 000000000..9313a71d4 --- /dev/null +++ b/pkg/controllers/ippool/ippool_controller_test.go @@ -0,0 +1,311 @@ +/* Copyright © 2021 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package ippool + +import ( + "context" + "errors" + "reflect" + "testing" + "time" + + "github.com/agiledragon/gomonkey/v2" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + mock_client "github.com/vmware-tanzu/nsx-operator/pkg/mock/controller-runtime/client" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/ippool" +) + +func NewFakeIPPoolReconciler() *IPPoolReconciler { + return &IPPoolReconciler{ + Client: fake.NewClientBuilder().Build(), + Scheme: fake.NewClientBuilder().Build().Scheme(), + Service: nil, + } +} + +func TestIPPoolController_setReadyStatusTrue(t *testing.T) { + r := NewFakeIPPoolReconciler() + ctx := context.TODO() + dummyIPPool := &v1alpha2.IPPool{} + + // Case: Static Route CRD creation fails + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX IPPool has been successfully created/updated", + Reason: "", + }, + } + r.setReadyStatusTrue(&ctx, dummyIPPool) + + if !reflect.DeepEqual(dummyIPPool.Status.Conditions, newConditions) { + t.Fatalf("Failed to correctly update Status Conditions when conditions haven't changed") + } +} + +type fakeStatusWriter struct { +} + +func (writer fakeStatusWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { + return nil +} +func (writer fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return nil +} +func (writer fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + return nil +} +func TestIPPoolReconciler_Reconcile(t *testing.T) { + + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + + service := &ippool.IPPoolService{ + Service: common.Service{ + NSXClient: &nsx.Client{}, + + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + service.NSXConfig.CoeConfig = &config.CoeConfig{} + service.NSXConfig.Cluster = "k8s_cluster" + r := &IPPoolReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + ctx := context.Background() + req := controllerruntime.Request{NamespacedName: types.NamespacedName{Namespace: "dummy", Name: "dummy"}} + + // not found + errNotFound := errors.New("not found") + k8sClient.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(errNotFound) + _, err := r.Reconcile(ctx, req) + assert.Equal(t, err, errNotFound) + + // DeletionTimestamp.IsZero = ture, client update failed + sp := &v1alpha2.IPPool{} + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha2.IPPool) + v1sp.Spec.Type = "Public" + return nil + }) + err = errors.New("Update failed") + k8sClient.EXPECT().Update(ctx, gomock.Any(), gomock.Any()).Return(err) + fakewriter := fakeStatusWriter{} + k8sClient.EXPECT().Status().Return(fakewriter) + _, ret := r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // DeletionTimestamp.IsZero = false, Finalizers doesn't include util.FinalizerName + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha2.IPPool) + time := metav1.Now() + v1sp.Spec.Type = "Public" + v1sp.ObjectMeta.DeletionTimestamp = &time + return nil + }) + + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteIPPool", func(_ *ippool.IPPoolService, uid interface{}) error { + assert.FailNow(t, "should not be called") + return nil + }) + + k8sClient.EXPECT().Update(ctx, gomock.Any(), gomock.Any()).Return(nil) + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = false, Finalizers include util.FinalizerName + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha2.IPPool) + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Spec.Type = "Public" + v1sp.Finalizers = []string{common.IPPoolFinalizerName} + return nil + }) + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteIPPool", func(_ *ippool.IPPoolService, uid interface{}) error { + return nil + }) + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = false, Finalizers include util.FinalizerName, DeleteIPPool fail + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha2.IPPool) + time := metav1.Now() + v1sp.Spec.Type = "Public" + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Finalizers = []string{common.IPPoolFinalizerName} + return nil + }) + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteIPPool", func(_ *ippool.IPPoolService, + uid interface{}) error { + return errors.New("delete failed") + }) + + k8sClient.EXPECT().Status().Times(2).Return(fakewriter) + _, ret = r.Reconcile(ctx, req) + assert.NotEqual(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = true, Finalizers include util.FinalizerName, CreateorUpdateIPPool fail + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha2.IPPool) + v1sp.ObjectMeta.DeletionTimestamp = nil + v1sp.Spec.Type = "Public" + v1sp.Finalizers = []string{common.IPPoolFinalizerName} + return nil + }) + + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "CreateOrUpdateIPPool", func(_ *ippool.IPPoolService, + obj *v1alpha2.IPPool) (bool, bool, error) { + return false, false, errors.New("create failed") + }) + _, ret = r.Reconcile(ctx, req) + assert.NotEqual(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = true, Finalizers include util.FinalizerName, CreateorUpdateIPPool succ + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha2.IPPool) + v1sp.ObjectMeta.DeletionTimestamp = nil + v1sp.Spec.Type = "Public" + v1sp.Finalizers = []string{common.IPPoolFinalizerName} + return nil + }) + + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "CreateOrUpdateIPPool", func(_ *ippool.IPPoolService, + obj *v1alpha2.IPPool) (bool, bool, error) { + return false, false, nil + }) + k8sClient.EXPECT().Status().Times(1).Return(fakewriter) + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, ret, nil) + patch.Reset() +} + +func TestReconciler_GarbageCollector(t *testing.T) { + // gc collect item "2345", local store has more item than k8s cache + service := &ippool.IPPoolService{ + Service: common.Service{ + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "ListIPPoolID", func(_ *ippool.IPPoolService) sets.String { + a := sets.NewString() + a.Insert("1234") + a.Insert("2345") + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteIPPool", func(_ *ippool.IPPoolService, UID interface{}) error { + return nil + }) + cancel := make(chan bool) + defer patch.Reset() + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + + r := &IPPoolReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + ctx := context.Background() + policyList := &v1alpha2.IPPoolList{} + k8sClient.EXPECT().List(gomock.Any(), policyList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha2.IPPoolList) + a.Items = append(a.Items, v1alpha2.IPPool{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.IPPoolGarbageCollector(cancel, time.Second) + + // local store has same item as k8s cache + patch.Reset() + patch.ApplyMethod(reflect.TypeOf(service), "ListIPPoolID", func(_ *ippool.IPPoolService) sets.String { + a := sets.NewString() + a.Insert("1234") + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteIPPool", func(_ *ippool.IPPoolService, UID interface{}) error { + assert.FailNow(t, "should not be called") + return nil + }) + k8sClient.EXPECT().List(ctx, policyList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha2.IPPoolList) + a.Items = append(a.Items, v1alpha2.IPPool{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.IPPoolGarbageCollector(cancel, time.Second) + + // local store has no item + patch.Reset() + patch.ApplyMethod(reflect.TypeOf(service), "ListIPPoolID", func(_ *ippool.IPPoolService) sets.String { + a := sets.NewString() + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteIPPool", func(_ *ippool.IPPoolService, UID interface{}) error { + assert.FailNow(t, "should not be called") + return nil + }) + k8sClient.EXPECT().List(ctx, policyList).Return(nil).Times(0) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.IPPoolGarbageCollector(cancel, time.Second) +} + +func TestReconciler_Start(t *testing.T) { + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + service := &ippool.IPPoolService{} + var mgr controllerruntime.Manager + r := &IPPoolReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + err := r.Start(mgr) + assert.NotEqual(t, err, nil) +} diff --git a/pkg/controllers/namespace/builder.go b/pkg/controllers/namespace/builder.go new file mode 100644 index 000000000..f13edf029 --- /dev/null +++ b/pkg/controllers/namespace/builder.go @@ -0,0 +1,19 @@ +package namespace + +import ( + "github.com/google/uuid" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" +) + +func BuildVPCCR(ns string, ncName string, vpcName *string) *v1alpha1.VPC { + log.V(2).Info("building vpc", "ns", ns, "nc", ncName, "VPC", vpcName) + vpc := &v1alpha1.VPC{} + if vpcName == nil { + vpc.Name = "vpc-" + uuid.New().String() + } else { + vpc.Name = *vpcName + } + + vpc.Namespace = ns + return vpc +} diff --git a/pkg/controllers/namespace/namespace_controller.go b/pkg/controllers/namespace/namespace_controller.go new file mode 100644 index 000000000..547ea661a --- /dev/null +++ b/pkg/controllers/namespace/namespace_controller.go @@ -0,0 +1,200 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package namespace + +import ( + "context" + "errors" + "fmt" + "runtime" + "strings" + + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + types "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + AnnotationNamespaceVPCError = " nsx.vmware.com/vpc_error" + + log = logger.Log +) + +// NamespaceReconciler process namespace create/delete event +type NamespaceReconciler struct { + Client client.Client + Scheme *apimachineryruntime.Scheme + NSXConfig *config.NSXOperatorConfig +} + +func (r *NamespaceReconciler) createVPCCR(ctx *context.Context, obj client.Object, ns string, ncName string, vpcName *string) (*v1alpha1.VPC, error) { + // check if vpc cr already exist under this namespace + vpcs := &v1alpha1.VPCList{} + r.Client.List(*ctx, vpcs, client.InNamespace(ns)) + if len(vpcs.Items) > 0 { + // if there is already one vpc exist under this ns, return this vpc. + log.Info("vpc cr already exist, skip creating", "VPC", vpcs.Items[0].Name) + return &vpcs.Items[0], nil + } + nc, ncExist := common.ServiceMediator.GetVPCNetworkConfig(ncName) + if !ncExist { + message := fmt.Sprintf("missing network config %s for namespace %s", ncName, ns) + r.namespaceError(ctx, obj, message, nil) + return nil, errors.New(message) + } + + //TODO: in next patch, remove this validation. If user did not provide private cidr + // use a hardcoded cidr to create a private ip block. + if !common.ServiceMediator.ValidateNetworkConfig(nc) { + // if netwrok config is not valid, no need to retry, skip processing + message := fmt.Sprintf("invalid network config %s for namespace %s, missing private cidr", ncName, ns) + r.namespaceError(ctx, obj, message, nil) + return nil, errors.New(message) + } + + // create vpc cr with exisitng vpc network config + vpcCR := BuildVPCCR(ns, ncName, vpcName) + err := r.Client.Create(*ctx, vpcCR) + if err != nil { + message := "failed to create VPC CR" + r.namespaceError(ctx, obj, message, err) + // If create VPC CR failed, put ns create event back to queue. + return nil, err + } + + changes := map[string]string{ + AnnotationNamespaceVPCError: "", + } + util.UpdateK8sResourceAnnotation(r.Client, ctx, obj, changes) + log.Info("create VPC CR", "VPC", vpcCR.Name, "Namespace", vpcCR.Namespace) + return vpcCR, nil +} + +func (r *NamespaceReconciler) namespaceError(ctx *context.Context, k8sObj client.Object, msg string, err error) { + logErr := util.If(err == nil, errors.New(msg), err).(error) + log.Error(logErr, msg) + changes := map[string]string{AnnotationNamespaceVPCError: msg} + util.UpdateK8sResourceAnnotation(r.Client, ctx, k8sObj, changes) +} + +func (r *NamespaceReconciler) insertNamespaceNetworkconfigBinding(ns string, anno map[string]string) { + ncName := "" + if anno == nil { + log.V(2).Info("empty annotation for namespace, using default network config", "Namespace", ns) + ncName = types.DefaultNetworkConfigName + } else { + annoNC, ncExist := anno[types.AnnotationVPCNetworkConfig] + if !ncExist { + ncName = types.DefaultNetworkConfigName + } else { + ncName = annoNC + } + } + + log.Info("record namespace and network config mapping relation", "Namespace", ns, "Networkconfig", ncName) + common.ServiceMediator.RegisterNamespaceNetworkconfigBinding(ns, ncName) +} + +/* + VPC creation strategy: + +We suppose namespace should have following annotations: + - "nsx.vmware.com/vpc_name": "/" + If the ns contains this annotation, first check if the namespace in annotation is the same as + the one in ns event, if yes, create an infra VPC for it. if not, skip the whole ns event as the infra + VPC will be created its corresponding ns creation event. + - "nsx.vmware.com/vpc_network_config":"" + If ns do not contains "nsx.vmware.com/vpc_name" annotation. Use this annotation to handle VPC creation. + VPC will locate the network config with the CR name, and create VPC using its config. + - If the ns do not have either of the annotation above, then we believe it is using default VPC, try to search + default VPC in network config CR store. The default VPC network config CR's name is "default". +*/ +func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + + obj := &v1.Namespace{} + log.Info("reconciling K8s namespace", "namespace", req.NamespacedName) + metrics.CounterInc(r.NSXConfig, metrics.ControllerSyncTotal, common.MetricResTypeNamespace) + + if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil { + log.Error(err, "unable to fetch namespace", "req", req.NamespacedName) + return common.ResultNormal, client.IgnoreNotFound(err) + } + + // processing create/update event + ns := obj.GetName() + if obj.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.NSXConfig, metrics.ControllerUpdateTotal, common.MetricResTypeNamespace) + log.Info("start processing namespace create/update event", "namespace", ns) + ctx := context.Background() + annotations := obj.GetAnnotations() + r.insertNamespaceNetworkconfigBinding(ns, annotations) + // read anno "nsx.vmware.com/vpc_name", if ns contains this annotation, it means it will share + // infra VPC, if the ns in the annotation is the same as ns event, create infra VPC, if not, + // skip the event. + ncName, ncExist := annotations[types.AnnotationVPCNetworkConfig] + vpcName, nameExist := annotations[types.AnnotationVPCName] + var create_vpc_name *string + if nameExist { + log.Info("read ns annotation vpcName", "VPCNAME", vpcName) + res := strings.Split(vpcName, "/") + // The format should be namespace/vpc_name + if len(res) != 2 { + message := fmt.Sprintf("incorrect vpcName annotation %s for namespace %s", vpcName, ns) + r.namespaceError(&ctx, obj, message, nil) + // If illegal format, skip handling this event? + return common.ResultNormal, nil + } + log.Info("start to handle vpcName anno", "VPCNS", res[1], "NS", ns) + + if ns != res[0] { + log.Info("name space is using shared vpc, with vpc name anno", "VPCNAME", vpcName, "Namespace", ns) + return common.ResultNormal, nil + } + create_vpc_name = &res[1] + log.Info("creating vpc using customer defined vpc name", "VPCName", res[1]) + } + + // If ns do not have network config name tag, then use default vpc network config name + if !ncExist { + log.Info("network config name not found on ns, using default network config", "Namespace", ns) + ncName = types.DefaultNetworkConfigName + } + + if _, err := r.createVPCCR(&ctx, obj, ns, ncName, create_vpc_name); err != nil { + return common.ResultRequeueAfter10sec, nil + } + return common.ResultNormal, nil + } else { + log.Info("skip ns deletion event for ns", "Namespace", ns) + metrics.CounterInc(r.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeNamespace) + common.ServiceMediator.UnRegisterNamespaceNetworkconfigBinding(obj.GetNamespace()) + return common.ResultNormal, nil + } +} + +func (r *NamespaceReconciler) setupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.Namespace{}). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Complete(r) +} + +// Start setup manager and launch GC +func (r *NamespaceReconciler) Start(mgr ctrl.Manager) error { + return r.setupWithManager(mgr) +} diff --git a/pkg/controllers/node/node_controller.go b/pkg/controllers/node/node_controller.go new file mode 100644 index 000000000..31d930070 --- /dev/null +++ b/pkg/controllers/node/node_controller.go @@ -0,0 +1,116 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package node + +import ( + "context" + "os" + + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/node" +) + +var ( + log = logger.Log + MetricResTypeNode = common.MetricResTypeNode +) + +// NodeReconciler reconciles a Node object +type NodeReconciler struct { + client.Client + Scheme *apimachineryruntime.Scheme + Service *node.NodeService +} + +func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + node := &v1.Node{} + deleted := false + log.Info("reconciling node", "node", req.NamespacedName) + + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResTypeNode) + + if err := r.Client.Get(ctx, req.NamespacedName, node); err != nil { + if errors.IsNotFound(err) { + log.Info("node not found", "req", req.NamespacedName) + deleted = true + } else { + log.Error(err, "unable to fetch node", "req", req.NamespacedName) + } + return common.ResultNormal, client.IgnoreNotFound(err) + } + if common.NodeIsMaster(node) { + // For WCP supervisor cluster, the master node isn't a transport node. + log.Info("skipping handling master node", "node", req.NamespacedName) + return ctrl.Result{}, nil + } + if !node.ObjectMeta.DeletionTimestamp.IsZero() { + log.Info("node is being deleted", "node", req.NamespacedName) + deleted = true + } + + if err := r.Service.SyncNodeStore(node.Name, deleted); err != nil { + log.Error(err, "failed to sync node store", "req", req.NamespacedName) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.Node{}). + Complete(r) +} + +func StartNodeController(mgr ctrl.Manager, commonService servicecommon.Service) { + nodePortReconciler := NodeReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if nodeService, err := node.InitializeNode(commonService); err != nil { + log.Error(err, "failed to initialize node commonService", "controller", "Node") + os.Exit(1) + } else { + nodePortReconciler.Service = nodeService + common.ServiceMediator.NodeService = nodePortReconciler.Service + } + + if err := nodePortReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "Node") + os.Exit(1) + } +} + +func (r *NodeReconciler) Start(mgr ctrl.Manager) error { + err := r.SetupWithManager(mgr) + if err != nil { + return err + } + return nil +} + +func updateFail(r *NodeReconciler, c *context.Context, o *v1.Node, e *error) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResTypeNode) +} + +func deleteFail(r *NodeReconciler, c *context.Context, o *v1.Node, e *error) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeNode) +} + +func updateSuccess(r *NodeReconciler, c *context.Context, o *v1.Node) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResTypeNode) +} + +func deleteSuccess(r *NodeReconciler, _ *context.Context, _ *v1.Node) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeNode) +} diff --git a/pkg/controllers/pod/pod_controller.go b/pkg/controllers/pod/pod_controller.go new file mode 100644 index 000000000..557fac487 --- /dev/null +++ b/pkg/controllers/pod/pod_controller.go @@ -0,0 +1,251 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package pod + +import ( + "context" + "os" + "runtime" + "strings" + "time" + + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetport" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + log = logger.Log + MetricResTypePod = common.MetricResTypePod +) + +// PodReconciler reconciles a Pod object +type PodReconciler struct { + client.Client + Scheme *apimachineryruntime.Scheme + Service *subnetport.SubnetPortService +} + +func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + pod := &v1.Pod{} + log.Info("reconciling pod", "pod", req.NamespacedName) + + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResTypePod) + + if err := r.Client.Get(ctx, req.NamespacedName, pod); err != nil { + log.Error(err, "unable to fetch pod", "req", req.NamespacedName) + return common.ResultNormal, client.IgnoreNotFound(err) + } + if pod.Spec.HostNetwork { + log.Info("skipping handling hostnetwork pod", "pod", req.NamespacedName) + return common.ResultNormal, nil + } + if len(pod.Spec.NodeName) == 0 { + log.Info("pod is not scheduled on node yet, skipping", "pod", req.NamespacedName) + return common.ResultNormal, nil + } + + if pod.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResTypePod) + if !controllerutil.ContainsFinalizer(pod, servicecommon.PodFinalizerName) { + controllerutil.AddFinalizer(pod, servicecommon.PodFinalizerName) + if err := r.Client.Update(ctx, pod); err != nil { + log.Error(err, "add finalizer", "pod", req.NamespacedName) + updateFail(r, &ctx, pod, &err) + return common.ResultRequeue, err + } + log.Info("added finalizer on pod", "pod", req.NamespacedName) + } + + nsxSubnetPath, err := r.GetSubnetPathForPod(ctx, pod) + if err != nil { + log.Error(err, "failed to get NSX resource path from subnet", "pod.Name", pod.Name, "pod.UID", pod.UID) + return common.ResultRequeue, err + } + log.Info("got NSX subnet for pod", "NSX subnet path", nsxSubnetPath, "pod.Name", pod.Name, "pod.UID", pod.UID) + node, err := common.ServiceMediator.GetNodeByName(pod.Spec.NodeName) + if err != nil { + // The error at the very beginning of the operator startup is expected because at that time the node may be not cached yet. We can expect the retry to become normal. + log.Error(err, "failed to get node ID for pod", "pod.Name", req.NamespacedName, "pod.UID", pod.UID, "node", pod.Spec.NodeName) + return common.ResultRequeue, err + } + contextID := *node.Id + nsxSubnetPortState, err := r.Service.CreateOrUpdateSubnetPort(pod, nsxSubnetPath, contextID, &pod.ObjectMeta.Labels) + if err != nil { + log.Error(err, "failed to create or update NSX subnet port, would retry exponentially", "pod.Name", req.NamespacedName, "pod.UID", pod.UID) + updateFail(r, &ctx, pod, &err) + return common.ResultRequeue, err + } + podAnnotationChanges := map[string]string{ + servicecommon.AnnotationPodMAC: strings.Trim(*nsxSubnetPortState.RealizedBindings[0].Binding.MacAddress, "\""), + servicecommon.AnnotationPodAttachment: *nsxSubnetPortState.Attachment.Id, + } + err = util.UpdateK8sResourceAnnotation(r.Client, &ctx, pod, podAnnotationChanges) + if err != nil { + log.Error(err, "failed to update pod annotation", "pod.Name", req.NamespacedName, "pod.UID", pod.UID, "podAnnotationChanges", podAnnotationChanges) + return common.ResultNormal, err + } + updateSuccess(r, &ctx, pod) + } else { + if controllerutil.ContainsFinalizer(pod, servicecommon.PodFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResTypePod) + if err := r.Service.DeleteSubnetPort(pod.UID); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "pod", req.NamespacedName) + deleteFail(r, &ctx, pod, &err) + return common.ResultRequeue, err + } + controllerutil.RemoveFinalizer(pod, servicecommon.PodFinalizerName) + if err := r.Client.Update(ctx, pod); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "pod", req.NamespacedName) + deleteFail(r, &ctx, pod, &err) + return common.ResultRequeue, err + } + log.Info("removed finalizer", "pod", req.NamespacedName) + deleteSuccess(r, &ctx, pod) + } else { + log.Info("finalizers cannot be recognized", "pod", req.NamespacedName) + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.Pod{}). + WithEventFilter( + predicate.Funcs{ + DeleteFunc: func(e event.DeleteEvent) bool { + // Suppress Delete events to avoid filtering them out in the Reconcile function + return false + }, + }, + ). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Complete(r) +} + +func StartPodController(mgr ctrl.Manager, commonService servicecommon.Service) { + podPortReconciler := PodReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if subnetPortService, err := subnetport.InitializeSubnetPort(commonService); err != nil { + log.Error(err, "failed to initialize subnetport commonService", "controller", "Pod") + os.Exit(1) + } else { + podPortReconciler.Service = subnetPortService + common.ServiceMediator.SubnetPortService = podPortReconciler.Service + } + if err := podPortReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "Pod") + os.Exit(1) + } +} + +// Start setup manager and launch GC +func (r *PodReconciler) Start(mgr ctrl.Manager) error { + err := r.SetupWithManager(mgr) + if err != nil { + return err + } + go r.GarbageCollector(make(chan bool), servicecommon.GCInterval) + return nil +} + +// GarbageCollector collect Pod which has been removed from crd. +// cancel is used to break the loop during UT +func (r *PodReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("pod garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxSubnetPortSet := r.Service.ListNSXSubnetPortIDForPod() + if len(nsxSubnetPortSet) == 0 { + continue + } + podList := &v1.PodList{} + err := r.Client.List(ctx, podList) + if err != nil { + log.Error(err, "failed to list Pod") + continue + } + + PodSet := sets.NewString() + for _, pod := range podList.Items { + PodSet.Insert(string(pod.UID)) + } + + for elem := range nsxSubnetPortSet { + if PodSet.Has(elem) { + continue + } + log.V(1).Info("GC collected Pod", "UID", elem) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResTypePod) + err = r.Service.DeleteSubnetPort(types.UID(elem)) + if err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypePod) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypePod) + } + } + } +} + +func updateFail(r *PodReconciler, c *context.Context, o *v1.Pod, e *error) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResTypePod) +} + +func deleteFail(r *PodReconciler, c *context.Context, o *v1.Pod, e *error) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypePod) +} + +func updateSuccess(r *PodReconciler, c *context.Context, o *v1.Pod) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResTypePod) +} + +func deleteSuccess(r *PodReconciler, _ *context.Context, _ *v1.Pod) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypePod) +} + +func (r *PodReconciler) GetSubnetPathForPod(ctx context.Context, pod *v1.Pod) (string, error) { + subnetPath := r.Service.GetSubnetPathForSubnetPortFromStore(string(pod.UID)) + if len(subnetPath) > 0 { + log.V(1).Info("NSX subnet port had been created, returning the existing NSX subnet path", "pod.UID", pod.UID, "subnetPath", subnetPath) + return subnetPath, nil + } + subnetSet, err := common.GetDefaultSubnetSet(r.Service.Client, ctx, pod.Namespace, servicecommon.LabelDefaultPodSubnetSet) + if err != nil { + return "", err + } + log.Info("got default subnetset for pod, allocating the NSX subnet", "subnetSet.Name", subnetSet.Name, "subnetSet.UID", subnetSet.UID, "pod.Name", pod.Name, "pod.UID", pod.UID) + subnetPath, err = common.AllocateSubnetFromSubnetSet(subnetSet) + if err != nil { + return subnetPath, err + } + log.Info("allocated NSX subnet for pod", "nsxSubnetPath", subnetPath, "pod.Name", pod.Name, "pod.UID", pod.UID) + return subnetPath, nil +} diff --git a/pkg/controllers/securitypolicy/namespace_controller.go b/pkg/controllers/securitypolicy/namespace_handler.go similarity index 97% rename from pkg/controllers/securitypolicy/namespace_controller.go rename to pkg/controllers/securitypolicy/namespace_handler.go index 3027f87bb..898b52c06 100644 --- a/pkg/controllers/securitypolicy/namespace_controller.go +++ b/pkg/controllers/securitypolicy/namespace_handler.go @@ -67,7 +67,7 @@ func (e *EnqueueRequestForNamespace) Update(updateEvent event.UpdateEvent, l wor err = reconcileSecurityPolicy(e.Client, podList.Items, l) if err != nil { - log.Error(err, "failed to reconcile security policy") + log.Error(err, "failed to reconcile security policy for namedport check") } } diff --git a/pkg/controllers/securitypolicy/namespace_controller_test.go b/pkg/controllers/securitypolicy/namespace_handler_test.go similarity index 100% rename from pkg/controllers/securitypolicy/namespace_controller_test.go rename to pkg/controllers/securitypolicy/namespace_handler_test.go diff --git a/pkg/controllers/securitypolicy/pod_controller.go b/pkg/controllers/securitypolicy/pod_handler.go similarity index 100% rename from pkg/controllers/securitypolicy/pod_controller.go rename to pkg/controllers/securitypolicy/pod_handler.go diff --git a/pkg/controllers/securitypolicy/pod_controller_test.go b/pkg/controllers/securitypolicy/pod_hanlder_test.go similarity index 100% rename from pkg/controllers/securitypolicy/pod_controller_test.go rename to pkg/controllers/securitypolicy/pod_hanlder_test.go diff --git a/pkg/controllers/securitypolicy/securitypolicy_controller.go b/pkg/controllers/securitypolicy/securitypolicy_controller.go index dd0ffae13..5f13c2b54 100644 --- a/pkg/controllers/securitypolicy/securitypolicy_controller.go +++ b/pkg/controllers/securitypolicy/securitypolicy_controller.go @@ -100,8 +100,8 @@ func (r *SecurityPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque if obj.ObjectMeta.DeletionTimestamp.IsZero() { metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResType) - if !controllerutil.ContainsFinalizer(obj, servicecommon.FinalizerName) { - controllerutil.AddFinalizer(obj, servicecommon.FinalizerName) + if !controllerutil.ContainsFinalizer(obj, servicecommon.SecurityPolicyFinalizerName) { + controllerutil.AddFinalizer(obj, servicecommon.SecurityPolicyFinalizerName) if err := r.Client.Update(ctx, obj); err != nil { log.Error(err, "add finalizer", "securitypolicy", req.NamespacedName) updateFail(r, &ctx, obj, &err) @@ -128,20 +128,20 @@ func (r *SecurityPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reque updateFail(r, &ctx, obj, &err) return ResultNormal, nil } - log.Error(err, "operate failed, would retry exponentially", "securitypolicy", req.NamespacedName) + log.Error(err, "create or update failed, would retry exponentially", "securitypolicy", req.NamespacedName) updateFail(r, &ctx, obj, &err) return ResultRequeue, err } updateSuccess(r, &ctx, obj) } else { - if controllerutil.ContainsFinalizer(obj, servicecommon.FinalizerName) { + if controllerutil.ContainsFinalizer(obj, servicecommon.SecurityPolicyFinalizerName) { metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) - if err := r.Service.DeleteSecurityPolicy(obj.UID); err != nil { + if err := r.Service.DeleteSecurityPolicy(obj, false); err != nil { log.Error(err, "deletion failed, would retry exponentially", "securitypolicy", req.NamespacedName) deleteFail(r, &ctx, obj, &err) return ResultRequeue, err } - controllerutil.RemoveFinalizer(obj, servicecommon.FinalizerName) + controllerutil.RemoveFinalizer(obj, servicecommon.SecurityPolicyFinalizerName) if err := r.Client.Update(ctx, obj); err != nil { log.Error(err, "deletion failed, would retry exponentially", "securitypolicy", req.NamespacedName) deleteFail(r, &ctx, obj, &err) @@ -177,7 +177,7 @@ func (r *SecurityPolicyReconciler) setSecurityPolicyReadyStatusFalse(ctx *contex Status: v1.ConditionFalse, Message: "NSX Security Policy could not be created/updated", Reason: fmt.Sprintf( - "error occurred while processing the Security Policy CR. Error: %v", + "error occurred while processing the SecurityPolicy CR. Error: %v", *err, ), }, @@ -194,7 +194,7 @@ func (r *SecurityPolicyReconciler) updateSecurityPolicyStatusConditions(ctx *con } if conditionsUpdated { r.Client.Status().Update(*ctx, sec_policy) - log.V(1).Info("updated Security Policy", "Name", sec_policy.Name, "Namespace", sec_policy.Namespace, + log.V(1).Info("updated SecurityPolicy", "Name", sec_policy.Name, "Namespace", sec_policy.Namespace, "New Conditions", newConditions) } } @@ -275,7 +275,7 @@ func (r *SecurityPolicyReconciler) GarbageCollector(cancel chan bool, timeout ti policyList := &v1alpha1.SecurityPolicyList{} err := r.Client.List(ctx, policyList) if err != nil { - log.Error(err, "failed to list security policy CR") + log.Error(err, "failed to list SecurityPolicy CR") continue } @@ -290,7 +290,7 @@ func (r *SecurityPolicyReconciler) GarbageCollector(cancel chan bool, timeout ti } log.V(1).Info("GC collected SecurityPolicy CR", "UID", elem) metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResType) - err = r.Service.DeleteSecurityPolicy(types.UID(elem)) + err = r.Service.DeleteSecurityPolicy(types.UID(elem), false) if err != nil { metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResType) } else { diff --git a/pkg/controllers/securitypolicy/securitypolicy_controller_test.go b/pkg/controllers/securitypolicy/securitypolicy_controller_test.go index 98c3352ad..285324854 100644 --- a/pkg/controllers/securitypolicy/securitypolicy_controller_test.go +++ b/pkg/controllers/securitypolicy/securitypolicy_controller_test.go @@ -171,18 +171,18 @@ func TestSecurityPolicyReconciler_Reconcile(t *testing.T) { // DeletionTimestamp.IsZero = ture, client update failed k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil) err = errors.New("Update failed") - k8sClient.EXPECT().Update(ctx, gomock.Any()).Return(err) + k8sClient.EXPECT().Update(ctx, gomock.Any(), gomock.Any()).Return(err) _, ret = r.Reconcile(ctx, req) assert.Equal(t, err, ret) - // DeletionTimestamp.IsZero = false, Finalizers doesn't include util.FinalizerName + // DeletionTimestamp.IsZero = false, Finalizers doesn't include util.SecurityPolicyFinalizerName k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { v1sp := obj.(*v1alpha1.SecurityPolicy) time := metav1.Now() v1sp.ObjectMeta.DeletionTimestamp = &time return nil }) - patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}) error { + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}, isVpcCleanup bool) error { assert.FailNow(t, "should not be called") return nil }) @@ -191,15 +191,15 @@ func TestSecurityPolicyReconciler_Reconcile(t *testing.T) { assert.Equal(t, ret, nil) patch.Reset() - // DeletionTimestamp.IsZero = false, Finalizers include util.FinalizerName + // DeletionTimestamp.IsZero = false, Finalizers include util.SecurityPolicyFinalizerName k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { v1sp := obj.(*v1alpha1.SecurityPolicy) time := metav1.Now() v1sp.ObjectMeta.DeletionTimestamp = &time - v1sp.Finalizers = []string{common.FinalizerName} + v1sp.Finalizers = []string{common.SecurityPolicyFinalizerName} return nil }) - patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}) error { + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}, isVpcCleanup bool) error { return nil }) _, ret = r.Reconcile(ctx, req) @@ -224,7 +224,7 @@ func TestSecurityPolicyReconciler_GarbageCollector(t *testing.T) { a.Insert("2345") return a }) - patch.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}) error { + patch.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}, isVpcCleanup bool) error { return nil }) cancel := make(chan bool) @@ -259,7 +259,7 @@ func TestSecurityPolicyReconciler_GarbageCollector(t *testing.T) { a.Insert("1234") return a }) - patch.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}) error { + patch.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}, isVpcCleanup bool) error { assert.FailNow(t, "should not be called") return nil }) @@ -282,7 +282,7 @@ func TestSecurityPolicyReconciler_GarbageCollector(t *testing.T) { a := sets.NewString() return a }) - patch.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}) error { + patch.ApplyMethod(reflect.TypeOf(service), "DeleteSecurityPolicy", func(_ *securitypolicy.SecurityPolicyService, UID interface{}, isVpcCleanup bool) error { assert.FailNow(t, "should not be called") return nil }) diff --git a/pkg/controllers/staticroute/staticroute_controller.go b/pkg/controllers/staticroute/staticroute_controller.go new file mode 100644 index 000000000..9c25efc55 --- /dev/null +++ b/pkg/controllers/staticroute/staticroute_controller.go @@ -0,0 +1,278 @@ +/* Copyright © 2021 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package staticroute + +import ( + "context" + "fmt" + "os" + "reflect" + "runtime" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + commonservice "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/staticroute" +) + +var ( + log = logger.Log + ResultNormal = common.ResultNormal + ResultRequeue = common.ResultRequeue + ResultRequeueAfter5mins = common.ResultRequeueAfter5mins + MetricResType = common.MetricResTypeStaticRoute +) + +// StaticRouteReconciler StaticRouteReconcile reconciles a StaticRoute object +type StaticRouteReconciler struct { + Client client.Client + Scheme *apimachineryruntime.Scheme + Service *staticroute.StaticRouteService +} + +func deleteFail(r *StaticRouteReconciler, c *context.Context, o *v1alpha1.StaticRoute, e *error) { + r.setStaticRouteReadyStatusFalse(c, o, e) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, common.MetricResTypeStaticRoute) +} + +func updateFail(r *StaticRouteReconciler, c *context.Context, o *v1alpha1.StaticRoute, e *error) { + r.setStaticRouteReadyStatusFalse(c, o, e) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResType) +} + +func updateSuccess(r *StaticRouteReconciler, c *context.Context, o *v1alpha1.StaticRoute) { + r.setStaticRouteReadyStatusTrue(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, common.MetricResTypeStaticRoute) +} + +func deleteSuccess(r *StaticRouteReconciler, _ *context.Context, _ *v1alpha1.StaticRoute) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, common.MetricResTypeStaticRoute) +} + +func (r *StaticRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + obj := &v1alpha1.StaticRoute{} + log.Info("reconciling staticroute CR", "staticroute", req.NamespacedName) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, common.MetricResTypeStaticRoute) + + if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil { + log.Error(err, "unable to fetch static route CR", "req", req.NamespacedName) + return ResultNormal, client.IgnoreNotFound(err) + } + + if obj.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, common.MetricResTypeStaticRoute) + if !controllerutil.ContainsFinalizer(obj, commonservice.StaticRouteFinalizerName) { + controllerutil.AddFinalizer(obj, commonservice.StaticRouteFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "add finalizer", "staticroute", req.NamespacedName) + updateFail(r, &ctx, obj, &err) + return ResultRequeue, err + } + log.V(1).Info("added finalizer on staticroute CR", "staticroute", req.NamespacedName) + } + + if err := r.Service.CreateOrUpdateStaticRoute(req.Namespace, obj); err != nil { + updateFail(r, &ctx, obj, &err) + // TODO: if error is not retriable, not requeue + return ResultRequeue, err + } + updateSuccess(r, &ctx, obj) + } else { + if controllerutil.ContainsFinalizer(obj, commonservice.StaticRouteFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeStaticRoute) + // TODO, update the value from 'default' to actual value, get OrgID, ProjectID, VPCID depending on obj.Namespace from vpc store + if err := r.Service.DeleteStaticRoute(req.Namespace, string(obj.UID)); err != nil { + log.Error(err, "delete failed, would retry exponentially", "staticroute", req.NamespacedName) + deleteFail(r, &ctx, obj, &err) + return ResultRequeue, err + } + controllerutil.RemoveFinalizer(obj, commonservice.StaticRouteFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + deleteFail(r, &ctx, obj, &err) + return ResultRequeue, err + } + log.V(1).Info("removed finalizer", "staticroute", req.NamespacedName) + deleteSuccess(r, &ctx, obj) + } else { + // only print a message because it's not a normal case + log.Info("finalizers cannot be recognized", "staticroute", req.NamespacedName) + } + } + + return ResultNormal, nil +} + +func (r *StaticRouteReconciler) setStaticRouteReadyStatusTrue(ctx *context.Context, static_route *v1alpha1.StaticRoute) { + newConditions := []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX Static Route has been successfully created/updated", + Reason: "NSX API returned 200 response code for PATCH", + }, + } + r.updateStaticRouteStatusConditions(ctx, static_route, newConditions) +} + +func (r *StaticRouteReconciler) setStaticRouteReadyStatusFalse(ctx *context.Context, static_route *v1alpha1.StaticRoute, err *error) { + newConditions := []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX Static Route could not be created/updated/deleted", + Reason: fmt.Sprintf("Error occurred while processing the Static Route CR. Please check the config and try again. Error: %v", *err), + }, + } + r.updateStaticRouteStatusConditions(ctx, static_route, newConditions) +} + +func (r *StaticRouteReconciler) updateStaticRouteStatusConditions(ctx *context.Context, static_route *v1alpha1.StaticRoute, newConditions []v1alpha1.StaticRouteCondition) { + conditionsUpdated := false + for i := range newConditions { + if r.mergeStaticRouteStatusCondition(static_route, &newConditions[i]) { + conditionsUpdated = true + } + } + if conditionsUpdated { + r.Client.Status().Update(*ctx, static_route) + log.V(1).Info("Updated Static Route CRD", "Name", static_route.Name, "Namespace", static_route.Namespace, "New Conditions", newConditions) + } +} + +func (r *StaticRouteReconciler) mergeStaticRouteStatusCondition(static_route *v1alpha1.StaticRoute, newCondition *v1alpha1.StaticRouteCondition) bool { + matchedCondition := getExistingConditionOfType(v1alpha1.StaticRouteStatusCondition(newCondition.Type), static_route.Status.Conditions) + + if reflect.DeepEqual(matchedCondition, newCondition) { + log.V(2).Info("Conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition) + return false + } + + if matchedCondition != nil { + matchedCondition.Reason = newCondition.Reason + matchedCondition.Message = newCondition.Message + matchedCondition.Status = newCondition.Status + } else { + static_route.Status.Conditions = append(static_route.Status.Conditions, *newCondition) + } + return true +} + +func getExistingConditionOfType(conditionType v1alpha1.StaticRouteStatusCondition, existingConditions []v1alpha1.StaticRouteCondition) *v1alpha1.StaticRouteCondition { + for i := range existingConditions { + if existingConditions[i].Type == v1alpha1.ConditionType(conditionType) { + return &existingConditions[i] + } + } + return nil +} + +func (r *StaticRouteReconciler) setupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.StaticRoute{}). + WithEventFilter(predicate.Funcs{ + DeleteFunc: func(e event.DeleteEvent) bool { + // Suppress Delete events to avoid filtering them out in the Reconcile function + return false + }, + }). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Complete(r) +} + +// Start setup manager and launch GC +func (r *StaticRouteReconciler) Start(mgr ctrl.Manager) error { + err := r.setupWithManager(mgr) + if err != nil { + return err + } + + go r.GarbageCollector(make(chan bool), commonservice.GCInterval) + return nil +} + +// GarbageCollector collect staticroute which has been removed from crd. +// cancel is used to break the loop during UT +func (r *StaticRouteReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxStaticRouteList := r.Service.ListStaticRoute() + if len(nsxStaticRouteList) == 0 { + continue + } + + crdStaticRouteList := &v1alpha1.StaticRouteList{} + err := r.Client.List(ctx, crdStaticRouteList) + if err != nil { + log.Error(err, "failed to list static route CR") + continue + } + + crdStaticRouteSet := sets.NewString() + for _, sr := range crdStaticRouteList.Items { + crdStaticRouteSet.Insert(string(sr.UID)) + } + + for _, elem := range nsxStaticRouteList { + UID := r.Service.GetUID(&elem) + if UID == nil { + continue + } + if crdStaticRouteSet.Has(*UID) { + continue + } + + log.V(1).Info("GC collected StaticRoute CR", "UID", elem) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeStaticRoute) + // get orgId, projectId, staticrouteId from path "/orgs//projects//vpcs//static-routes/" + path := strings.Split(*elem.Path, "/") + err = r.Service.DeleteStaticRouteByPath(path[2], path[4], path[6], *elem.Id) + if err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, common.MetricResTypeStaticRoute) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, common.MetricResTypeStaticRoute) + } + } + } +} + +func StartStaticRouteController(mgr ctrl.Manager, commonService commonservice.Service) { + staticRouteReconcile := StaticRouteReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if staticRouteService, err := staticroute.InitializeStaticRoute(commonService); err != nil { + log.Error(err, "failed to initialize staticroute commonService", "controller", "StaticRoute") + os.Exit(1) + } else { + staticRouteReconcile.Service = staticRouteService + } + if err := staticRouteReconcile.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "StaticRoute") + os.Exit(1) + } +} diff --git a/pkg/controllers/staticroute/staticroute_controller_test.go b/pkg/controllers/staticroute/staticroute_controller_test.go new file mode 100644 index 000000000..2d1e84aec --- /dev/null +++ b/pkg/controllers/staticroute/staticroute_controller_test.go @@ -0,0 +1,368 @@ +/* Copyright © 2021 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package staticroute + +import ( + "context" + "errors" + "reflect" + "testing" + "time" + + gomonkey "github.com/agiledragon/gomonkey/v2" + "github.com/golang/mock/gomock" + "github.com/openlyinc/pointy" + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + mock_client "github.com/vmware-tanzu/nsx-operator/pkg/mock/controller-runtime/client" + mocks "github.com/vmware-tanzu/nsx-operator/pkg/mock/staticrouteclient" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/staticroute" +) + +func NewFakeStaticRouteReconciler() *StaticRouteReconciler { + return &StaticRouteReconciler{ + Client: fake.NewClientBuilder().Build(), + Scheme: fake.NewClientBuilder().Build().Scheme(), + Service: nil, + } +} + +func TestStaticRouteController_updateStaticRouteStatusConditions(t *testing.T) { + r := NewFakeStaticRouteReconciler() + ctx := context.TODO() + dummySR := &v1alpha1.StaticRoute{} + + // Case: Static Route CRD creation fails + newConditions := []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX Static Route could not be created/updated", + Reason: "Error occurred while processing the Static Route CRD. Please check the config and try again", + }, + } + r.updateStaticRouteStatusConditions(&ctx, dummySR, newConditions) + + if !reflect.DeepEqual(dummySR.Status.Conditions, newConditions) { + t.Fatalf("Failed to correctly update Status Conditions when conditions haven't changed") + } + + // Case: No change in Conditions + dummyConditions := []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX Static Route could not be created/updated", + Reason: "Error occurred while processing the Static Route CRD. Please check the config and try again", + }, + } + dummySR.Status.Conditions = dummyConditions + + newConditions = []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX Static Route could not be created/updated", + Reason: "Error occurred while processing the Static Route CRD. Please check the config and try again", + }, + } + + r.updateStaticRouteStatusConditions(&ctx, dummySR, newConditions) + + if !reflect.DeepEqual(dummySR.Status.Conditions, newConditions) { + t.Fatalf("Failed to correctly update Status Conditions when conditions haven't changed") + } + + // Case: SP CRD Creation succeeds after failure + newConditions = []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX Static Route has been successfully created/updated", + Reason: "NSX API returned 200 response code for PATCH", + }, + } + + r.updateStaticRouteStatusConditions(&ctx, dummySR, newConditions) + + if !reflect.DeepEqual(dummySR.Status.Conditions, newConditions) { + t.Fatalf("Failed to correctly update Status Conditions when conditions haven't changed") + } + + // Case: SP CRD Update failed + newConditions = []v1alpha1.StaticRouteCondition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX Static Route could not be created/updated", + Reason: "Error occurred while processing the Static Route CRD. Please check the config and try again", + }, + } + + r.updateStaticRouteStatusConditions(&ctx, dummySR, newConditions) + + if !reflect.DeepEqual(dummySR.Status.Conditions, newConditions) { + t.Fatalf("Failed to correctly update Status Conditions when conditions haven't changed") + } +} + +type fakeStatusWriter struct { +} + +func (writer fakeStatusWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { + return nil +} +func (writer fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + return nil +} +func (writer fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + return nil +} +func TestStaticRouteReconciler_Reconcile(t *testing.T) { + + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + + mockCtrl := gomock.NewController(t) + mockStaticRouteclient := mocks.NewMockStaticRoutesClient(mockCtrl) + + service := &staticroute.StaticRouteService{ + Service: common.Service{ + NSXClient: &nsx.Client{ + StaticRouteClient: mockStaticRouteclient, + }, + + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + service.NSXConfig.CoeConfig = &config.CoeConfig{} + service.NSXConfig.Cluster = "k8s_cluster" + r := &StaticRouteReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + ctx := context.Background() + req := controllerruntime.Request{NamespacedName: types.NamespacedName{Namespace: "dummy", Name: "dummy"}} + + // not found + errNotFound := errors.New("not found") + k8sClient.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(errNotFound) + _, err := r.Reconcile(ctx, req) + assert.Equal(t, err, errNotFound) + + // DeletionTimestamp.IsZero = ture, client update failed + sp := &v1alpha1.StaticRoute{} + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil) + err = errors.New("Update failed") + k8sClient.EXPECT().Update(ctx, gomock.Any(), gomock.Any()).Return(err) + fakewriter := fakeStatusWriter{} + k8sClient.EXPECT().Status().Return(fakewriter) + _, ret := r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // DeletionTimestamp.IsZero = false, Finalizers doesn't include util.FinalizerName + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.StaticRoute) + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + return nil + }) + + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, uid string) error { + assert.FailNow(t, "should not be called") + return nil + }) + + k8sClient.EXPECT().Update(ctx, gomock.Any(), gomock.Any()).Return(nil) + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = false, Finalizers include util.FinalizerName + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.StaticRoute) + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Finalizers = []string{common.StaticRouteFinalizerName} + return nil + }) + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, uid string) error { + return nil + }) + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = false, Finalizers include util.FinalizerName, DeleteStaticRoute fail + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.StaticRoute) + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Finalizers = []string{common.StaticRouteFinalizerName} + return nil + }) + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "DeleteStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, uid string) error { + return errors.New("delete failed") + }) + + k8sClient.EXPECT().Status().Times(2).Return(fakewriter) + _, ret = r.Reconcile(ctx, req) + assert.NotEqual(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = true, Finalizers include util.FinalizerName, CreateorUpdateStaticRoute fail + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.StaticRoute) + v1sp.ObjectMeta.DeletionTimestamp = nil + v1sp.Finalizers = []string{common.StaticRouteFinalizerName} + return nil + }) + + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "CreateOrUpdateStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, obj *v1alpha1.StaticRoute) error { + return errors.New("create failed") + }) + _, ret = r.Reconcile(ctx, req) + assert.NotEqual(t, ret, nil) + patch.Reset() + + // DeletionTimestamp.IsZero = true, Finalizers include util.FinalizerName, CreateorUpdateStaticRoute succ + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do(func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.StaticRoute) + v1sp.ObjectMeta.DeletionTimestamp = nil + v1sp.Finalizers = []string{common.StaticRouteFinalizerName} + return nil + }) + + patch = gomonkey.ApplyMethod(reflect.TypeOf(service), "CreateOrUpdateStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, obj *v1alpha1.StaticRoute) error { + return nil + }) + k8sClient.EXPECT().Status().Times(1).Return(fakewriter) + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, ret, nil) + patch.Reset() +} + +func TestStaticRouteReconciler_GarbageCollector(t *testing.T) { + // gc collect item "2345", local store has more item than k8s cache + service := &staticroute.StaticRouteService{ + Service: common.Service{ + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "ListStaticRoute", func(_ *staticroute.StaticRouteService) []model.StaticRoutes { + a := []model.StaticRoutes{} + id1 := "2345" + path := "/orgs/org123/projects/pro123/vpcs/vpc123/static-routes/123" + tag1 := []model.Tag{{Scope: pointy.String(common.TagScopeStaticRouteCRUID), Tag: pointy.String("2345")}} + a = append(a, model.StaticRoutes{Id: &id1, Path: &path, Tags: tag1}) + id2 := "1234" + tag2 := []model.Tag{{Scope: pointy.String(common.TagScopeStaticRouteCRUID), Tag: pointy.String("1234")}} + a = append(a, model.StaticRoutes{Id: &id2, Path: &path, Tags: tag2}) + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteStaticRouteByPath", func(_ *staticroute.StaticRouteService, orgId string, projectId string, vpcId string, uid string) error { + return nil + }) + cancel := make(chan bool) + defer patch.Reset() + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + + r := &StaticRouteReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + ctx := context.Background() + srList := &v1alpha1.StaticRouteList{} + k8sClient.EXPECT().List(ctx, srList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha1.StaticRouteList) + a.Items = append(a.Items, v1alpha1.StaticRoute{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) + + // local store has same item as k8s cache + patch.Reset() + patch.ApplyMethod(reflect.TypeOf(service), "ListStaticRoute", func(_ *staticroute.StaticRouteService) []model.StaticRoutes { + a := []model.StaticRoutes{} + id := "1234" + tag2 := []model.Tag{{Scope: pointy.String(common.TagScopeStaticRouteCRUID), Tag: pointy.String(id)}} + a = append(a, model.StaticRoutes{Id: &id, Tags: tag2}) + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, uid string) error { + assert.FailNow(t, "should not be called") + return nil + }) + k8sClient.EXPECT().List(gomock.Any(), srList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha1.StaticRouteList) + a.Items = append(a.Items, v1alpha1.StaticRoute{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) + + // local store has no item + patch.Reset() + patch.ApplyMethod(reflect.TypeOf(service), "ListStaticRoute", func(_ *staticroute.StaticRouteService) []model.StaticRoutes { + return []model.StaticRoutes{} + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteStaticRoute", func(_ *staticroute.StaticRouteService, namespace string, uid string) error { + assert.FailNow(t, "should not be called") + return nil + }) + k8sClient.EXPECT().List(ctx, srList).Return(nil).Times(0) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) +} + +func TestStaticRouteReconciler_Start(t *testing.T) { + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + service := &staticroute.StaticRouteService{} + var mgr controllerruntime.Manager + r := &StaticRouteReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + err := r.Start(mgr) + assert.NotEqual(t, err, nil) +} diff --git a/pkg/controllers/subnet/namespace_handler.go b/pkg/controllers/subnet/namespace_handler.go new file mode 100644 index 000000000..9763c3289 --- /dev/null +++ b/pkg/controllers/subnet/namespace_handler.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package subnet + +import ( + "context" + "reflect" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" +) + +// Subnet controller should watch event of namespace, when there are some updates of namespace labels, +// controller should build tags and update VpcSubnet according to new labels. + +type EnqueueRequestForNamespace struct { + Client client.Client +} + +func (e *EnqueueRequestForNamespace) Create(_ event.CreateEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("namespace create event, do nothing") +} + +func (e *EnqueueRequestForNamespace) Delete(_ event.DeleteEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("namespace delete event, do nothing") +} + +func (e *EnqueueRequestForNamespace) Generic(_ event.GenericEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("namespace generic event, do nothing") +} + +func (e *EnqueueRequestForNamespace) Update(updateEvent event.UpdateEvent, l workqueue.RateLimitingInterface) { + obj := updateEvent.ObjectNew.(*v1.Namespace) + err := reconcileSubnet(e.Client, obj.Name, l) + if err != nil { + log.Error(err, "failed to reconcile subnet") + } +} + +var PredicateFuncsNs = predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObj := e.ObjectOld.(*v1.Namespace) + newObj := e.ObjectNew.(*v1.Namespace) + log.V(1).Info("receive namespace update event", "name", oldObj.Name) + if reflect.DeepEqual(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) { + log.Info("labels of namespace are not changed", "name", oldObj.Name) + return false + } + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, +} + +func reconcileSubnet(c client.Client, namespace string, q workqueue.RateLimitingInterface) error { + subnetList := &v1alpha1.SubnetList{} + err := c.List(context.Background(), subnetList, client.InNamespace(namespace)) + if err != nil { + log.Error(err, "failed to list all the subnets") + return err + } + + for _, subnet_item := range subnetList.Items { + log.Info("reconcile subnet because namespace update", + "namespace", subnet_item.Namespace, "name", subnet_item.Name) + q.Add(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: subnet_item.Name, + Namespace: subnet_item.Namespace, + }, + }) + } + return nil +} diff --git a/pkg/controllers/subnet/subnet_controller.go b/pkg/controllers/subnet/subnet_controller.go new file mode 100644 index 000000000..694838086 --- /dev/null +++ b/pkg/controllers/subnet/subnet_controller.go @@ -0,0 +1,342 @@ +package subnet + +import ( + "context" + "errors" + "fmt" + "reflect" + "runtime" + "time" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet" +) + +var ( + log = logger.Log + ResultNormal = common.ResultNormal + ResultRequeue = common.ResultRequeue + ResultRequeueAfter5mins = common.ResultRequeueAfter5mins + MetricResTypeSubnet = common.MetricResTypeSubnet +) + +// SubnetReconciler reconciles a SubnetSet object +type SubnetReconciler struct { + Client client.Client + Scheme *apimachineryruntime.Scheme + Service *subnet.SubnetService +} + +func (r *SubnetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + obj := &v1alpha1.Subnet{} + nsObj := &v1.Namespace{} + log.Info("reconciling subnet CR", "subnet", req.NamespacedName) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResTypeSubnet) + + if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil { + log.Error(err, "unable to fetch Subnet CR", "req", req.NamespacedName) + return ResultNormal, client.IgnoreNotFound(err) + } + if err := r.Client.Get(ctx, client.ObjectKey{Name: obj.Namespace}, nsObj); err != nil { + err = fmt.Errorf("unable to fetch namespace %s", obj.Namespace) + log.Error(err, "") + return ResultRequeue, err + } + + if obj.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResTypeSubnet) + if !controllerutil.ContainsFinalizer(obj, servicecommon.SubnetFinalizerName) { + controllerutil.AddFinalizer(obj, servicecommon.SubnetFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "add finalizer", "subnet", req.NamespacedName) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + log.V(1).Info("added finalizer on subnet CR", "subnet", req.NamespacedName) + } + if obj.Spec.AccessMode == "" || obj.Spec.IPv4SubnetSize == 0 { + vpcNetworkConfig := commonctl.ServiceMediator.GetVPCNetworkConfigByNamespace(obj.Namespace) + if vpcNetworkConfig == nil { + err := fmt.Errorf("operate failed: cannot get configuration for Subnet CR") + log.Error(nil, "failed to find VPCNetworkConfig for Subnet CR", "subnet", req.NamespacedName, "namespace %s", obj.Namespace) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + if obj.Spec.AccessMode == "" { + obj.Spec.AccessMode = v1alpha1.AccessMode(vpcNetworkConfig.DefaultSubnetAccessMode) + } + if obj.Spec.IPv4SubnetSize == 0 { + obj.Spec.IPv4SubnetSize = vpcNetworkConfig.DefaultIPv4SubnetSize + } + } + + namespace := &v1.Namespace{} + namespacedName := types.NamespacedName{ + Name: req.Namespace, + } + if err := r.Client.Get(context.Background(), namespacedName, namespace); err != nil { + log.Error(err, "unable to fetch namespace of Subnet CR", "req", req.NamespacedName) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + tags := r.Service.GenerateSubnetNSTags(obj, string(nsObj.UID)) + for k, v := range nsObj.Labels { + tags = append(tags, model.Tag{Scope: servicecommon.String(k), Tag: servicecommon.String(v)}) + } + + if _, err := r.Service.CreateOrUpdateSubnet(obj, tags); err != nil { + log.Error(err, "operate failed, would retry exponentially", "subnet", req.NamespacedName) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + if err := r.updateSubnetStatus(obj); err != nil { + log.Error(err, "update subnet status failed, would retry exponentially", "subnet", req.NamespacedName) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + updateSuccess(r, &ctx, obj) + } else { + if controllerutil.ContainsFinalizer(obj, servicecommon.SubnetFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResTypeSubnet) + if err := r.DeleteSubnet(*obj); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "subnet", req.NamespacedName) + deleteFail(r, &ctx, obj) + return ResultRequeue, err + } + controllerutil.RemoveFinalizer(obj, servicecommon.SubnetFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "subnet", req.NamespacedName) + deleteFail(r, &ctx, obj) + return ResultRequeue, err + } + log.V(1).Info("removed finalizer", "subnet", req.NamespacedName) + deleteSuccess(r, &ctx, obj) + } else { + log.Info("finalizers cannot be recognized", "subnet", req.NamespacedName) + } + } + return ctrl.Result{}, nil +} + +func (r *SubnetReconciler) DeleteSubnet(obj v1alpha1.Subnet) error { + nsxSubnets := r.Service.SubnetStore.GetByIndex(servicecommon.TagScopeSubnetCRUID, string(obj.GetUID())) + if len(nsxSubnets) == 0 { + log.Info("no subnet found for subnet CR", "uid", string(obj.GetUID())) + return nil + } + portNums := len(common.ServiceMediator.GetPortsOfSubnet(*nsxSubnets[0].Id)) + if portNums > 0 { + err := errors.New("subnet still attached by port") + log.Error(err, "", "ID", *nsxSubnets[0].Id) + return err + } + return r.Service.DeleteSubnet(nsxSubnets[0]) +} + +func (r *SubnetReconciler) updateSubnetStatus(obj *v1alpha1.Subnet) error { + nsxSubnet := r.Service.SubnetStore.GetByKey(r.Service.BuildSubnetID(obj)) + if nsxSubnet == nil { + return errors.New("failed to get NSX Subnet from store") + } + obj.Status.IPAddresses = obj.Status.IPAddresses[:0] + statusList, err := r.Service.GetSubnetStatus(nsxSubnet) + if err != nil { + return err + } + for _, status := range statusList { + obj.Status.IPAddresses = append(obj.Status.IPAddresses, *status.NetworkAddress) + } + obj.Status.NSXResourcePath = *nsxSubnet.Path + return nil +} + +func (r *SubnetReconciler) setSubnetReadyStatusTrue(ctx *context.Context, subnet *v1alpha1.Subnet) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX Subnet has been successfully created/updated", + Reason: "SubnetCreated", + }, + } + r.updateSubnetStatusConditions(ctx, subnet, newConditions) +} + +func (r *SubnetReconciler) setSubnetReadyStatusFalse(ctx *context.Context, subnet *v1alpha1.Subnet) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX Subnet could not be created/updated", + Reason: "SubnetNotReady", + }, + } + r.updateSubnetStatusConditions(ctx, subnet, newConditions) +} + +func (r *SubnetReconciler) updateSubnetStatusConditions(ctx *context.Context, subnet *v1alpha1.Subnet, newConditions []v1alpha1.Condition) { + conditionsUpdated := false + for i := range newConditions { + if r.mergeSubnetStatusCondition(ctx, subnet, &newConditions[i]) { + conditionsUpdated = true + } + } + if conditionsUpdated { + r.Client.Status().Update(*ctx, subnet) + log.V(1).Info("updated Subnet", "Name", subnet.Name, "Namespace", subnet.Namespace, + "New Conditions", newConditions) + } +} + +func (r *SubnetReconciler) mergeSubnetStatusCondition(ctx *context.Context, subnet *v1alpha1.Subnet, newCondition *v1alpha1.Condition) bool { + matchedCondition := getExistingConditionOfType(newCondition.Type, subnet.Status.Conditions) + + if reflect.DeepEqual(matchedCondition, newCondition) { + log.V(2).Info("conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition) + return false + } + + if matchedCondition != nil { + matchedCondition.Reason = newCondition.Reason + matchedCondition.Message = newCondition.Message + matchedCondition.Status = newCondition.Status + } else { + subnet.Status.Conditions = append(subnet.Status.Conditions, *newCondition) + } + return true +} + +func getExistingConditionOfType(conditionType v1alpha1.ConditionType, existingConditions []v1alpha1.Condition) *v1alpha1.Condition { + for i := range existingConditions { + if existingConditions[i].Type == conditionType { + return &existingConditions[i] + } + } + return nil +} + +func updateFail(r *SubnetReconciler, c *context.Context, o *v1alpha1.Subnet) { + r.setSubnetReadyStatusFalse(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResTypeSubnet) +} + +func deleteFail(r *SubnetReconciler, c *context.Context, o *v1alpha1.Subnet) { + r.setSubnetReadyStatusFalse(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeSubnet) +} + +func updateSuccess(r *SubnetReconciler, c *context.Context, o *v1alpha1.Subnet) { + r.setSubnetReadyStatusTrue(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResTypeSubnet) +} + +func deleteSuccess(r *SubnetReconciler, _ *context.Context, _ *v1alpha1.Subnet) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeSubnet) +} + +func (r *SubnetReconciler) setupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.Subnet{}). + WithEventFilter(predicate.Funcs{ + DeleteFunc: func(e event.DeleteEvent) bool { + // Suppress Delete events to avoid filtering them out in the Reconcile function + return false + }, + }). + Watches( + &source.Kind{Type: &v1.Namespace{}}, + &EnqueueRequestForNamespace{Client: mgr.GetClient()}, + builder.WithPredicates(PredicateFuncsNs), + ). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Complete(r) +} + +func StartSubnetController(mgr ctrl.Manager, commonService servicecommon.Service) error { + subnetReconciler := &SubnetReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + subnetReconciler.Service = subnet.GetSubnetService(commonService) + common.ServiceMediator.SubnetService = subnetReconciler.Service + if err := subnetReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "Subnet") + return err + } + return nil +} + +// Start setup manager +func (r *SubnetReconciler) Start(mgr ctrl.Manager) error { + err := r.setupWithManager(mgr) + if err != nil { + return err + } + go r.GarbageCollector(make(chan bool), servicecommon.GCInterval) + return nil +} + +func (r *SubnetReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("subnet garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxSubnetList := r.Service.ListSubnetCreatedByCR() + if len(nsxSubnetList) == 0 { + continue + } + + crdSubnetList := &v1alpha1.SubnetList{} + err := r.Client.List(ctx, crdSubnetList) + if err != nil { + log.Error(err, "failed to list subnet CR") + continue + } + + crdSubnetIDs := sets.NewString() + for _, sr := range crdSubnetList.Items { + crdSubnetIDs.Insert(string(sr.UID)) + } + + for _, elem := range nsxSubnetList { + if crdSubnetIDs.Has(*elem.Id) { + continue + } + + log.Info("GC collected Subnet CR", "UID", elem) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeSubnet) + err = r.Service.DeleteSubnet(elem) + if err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, common.MetricResTypeSubnet) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, common.MetricResTypeSubnet) + } + } + } +} diff --git a/pkg/controllers/subnet/subnet_controller_test.go b/pkg/controllers/subnet/subnet_controller_test.go new file mode 100644 index 000000000..25852d263 --- /dev/null +++ b/pkg/controllers/subnet/subnet_controller_test.go @@ -0,0 +1,112 @@ +package subnet + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/agiledragon/gomonkey" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + mock_client "github.com/vmware-tanzu/nsx-operator/pkg/mock/controller-runtime/client" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet" +) + +func TestSubnetReconciler_GarbageCollector(t *testing.T) { + service := &subnet.SubnetService{ + Service: common.Service{ + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + // Subnet doesn't have TagScopeSubnetSetCRId (not belong to SubnetSet) + // gc collect item "2345", local store has more item than k8s cache + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "ListSubnetCreatedByCR", func(_ *subnet.SubnetService) []model.VpcSubnet { + a := []model.VpcSubnet{} + id1 := "2345" + a = append(a, model.VpcSubnet{Id: &id1}) + id2 := "1234" + a = append(a, model.VpcSubnet{Id: &id2}) + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteSubnet", func(_ *subnet.SubnetService, subnet model.VpcSubnet) error { + return nil + }) + cancel := make(chan bool) + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + + r := &SubnetReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + ctx := context.Background() + srList := &v1alpha1.SubnetList{} + k8sClient.EXPECT().List(ctx, srList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha1.SubnetList) + a.Items = append(a.Items, v1alpha1.Subnet{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) + + // local store has same item as k8s cache + patch.Reset() + patch.ApplyMethod(reflect.TypeOf(service), "ListSubnetCreatedByCR", func(_ *subnet.SubnetService) []model.VpcSubnet { + a := []model.VpcSubnet{} + id := "1234" + a = append(a, model.VpcSubnet{Id: &id}) + return a + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteSubnet", func(_ *subnet.SubnetService, subnet model.VpcSubnet) error { + assert.FailNow(t, "should not be called") + return nil + }) + k8sClient.EXPECT().List(gomock.Any(), srList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha1.SubnetList) + a.Items = append(a.Items, v1alpha1.Subnet{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) + + // local store has no item + patch.Reset() + patch.ApplyMethod(reflect.TypeOf(service), "ListSubnetCreatedByCR", func(_ *subnet.SubnetService) []model.VpcSubnet { + return []model.VpcSubnet{} + }) + patch.ApplyMethod(reflect.TypeOf(service), "DeleteSubnet", func(_ *subnet.SubnetService, subnet model.VpcSubnet) error { + assert.FailNow(t, "should not be called") + return nil + }) + k8sClient.EXPECT().List(ctx, srList).Return(nil).Times(0) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) + patch.Reset() +} diff --git a/pkg/controllers/subnetport/subnetport_controller.go b/pkg/controllers/subnetport/subnetport_controller.go new file mode 100644 index 000000000..2b8a7f30f --- /dev/null +++ b/pkg/controllers/subnetport/subnetport_controller.go @@ -0,0 +1,443 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package subnetport + +import ( + "context" + "errors" + "fmt" + "os" + "reflect" + "runtime" + "strings" + "time" + + vmv1alpha1 "github.com/vmware-tanzu/vm-operator/api/v1alpha1" + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/util/retry" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetport" +) + +var ( + log = logger.Log + MetricResTypeSubnetPort = common.MetricResTypeSubnetPort +) + +// SubnetPortReconciler reconciles a SubnetPort object +type SubnetPortReconciler struct { + client.Client + Scheme *apimachineryruntime.Scheme + Service *subnetport.SubnetPortService +} + +// +kubebuilder:rbac:groups=nsx.vmware.com,resources=subnetports,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=nsx.vmware.com,resources=subnetports/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=nsx.vmware.com,resources=subnetports/finalizers,verbs=update +func (r *SubnetPortReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + subnetPort := &v1alpha1.SubnetPort{} + log.Info("reconciling subnetport CR", "subnetport", req.NamespacedName) + + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResTypeSubnetPort) + + if err := r.Client.Get(ctx, req.NamespacedName, subnetPort); err != nil { + log.Error(err, "unable to fetch subnetport CR", "req", req.NamespacedName) + return common.ResultNormal, client.IgnoreNotFound(err) + } + + if len(subnetPort.Spec.SubnetSet) > 0 && len(subnetPort.Spec.Subnet) > 0 { + err := errors.New("subnet and subnetset should not be configured at the same time") + log.Error(err, "failed to get subnet/subnetset of the subnetport", "subnetport", req.NamespacedName) + return common.ResultNormal, err + } + + if subnetPort.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResTypeSubnetPort) + if !controllerutil.ContainsFinalizer(subnetPort, servicecommon.SubnetPortFinalizerName) { + controllerutil.AddFinalizer(subnetPort, servicecommon.SubnetPortFinalizerName) + if err := r.Client.Update(ctx, subnetPort); err != nil { + log.Error(err, "add finalizer", "subnetport", req.NamespacedName) + updateFail(r, &ctx, subnetPort, &err) + return common.ResultRequeue, err + } + log.Info("added finalizer on subnetport CR", "subnetport", req.NamespacedName) + } + + old_status := subnetPort.Status.DeepCopy() + nsxSubnetPath, err := r.GetSubnetPathForSubnetPort(ctx, subnetPort) + if err != nil { + log.Error(err, "failed to get NSX resource path from subnet", "subnetport", subnetPort) + return common.ResultRequeue, err + } + labels, err := r.getLabelsFromVirtualMachine(ctx, subnetPort) + if err != nil { + log.Error(err, "failed to get labels from virtualmachine", "subnetPort.Name", subnetPort.Name, "subnetPort.UID", subnetPort.UID, "subnetPort.Spec.AttachmentRef", subnetPort.Spec.AttachmentRef) + return common.ResultRequeue, err + } + log.Info("got labels from virtualmachine for subnetport", "subnetPort.UID", subnetPort.UID, "virtualmachine name", subnetPort.Spec.AttachmentRef.Name, "labels", labels) + nsxSubnetPortState, err := r.Service.CreateOrUpdateSubnetPort(subnetPort, nsxSubnetPath, "", labels) + if err != nil { + log.Error(err, "failed to create or update NSX subnet port, would retry exponentially", "subnetport", req.NamespacedName) + updateFail(r, &ctx, subnetPort, &err) + return common.ResultRequeue, err + } + ipAddress := v1alpha1.SubnetPortIPAddress{ + IP: *nsxSubnetPortState.RealizedBindings[0].Binding.IpAddress, + } + subnetPort.Status.IPAddresses = []v1alpha1.SubnetPortIPAddress{ipAddress} + subnetPort.Status.MACAddress = strings.Trim(*nsxSubnetPortState.RealizedBindings[0].Binding.MacAddress, "\"") + subnetPort.Status.VIFID = *nsxSubnetPortState.Attachment.Id + err = r.updateSubnetStatusOnSubnetPort(subnetPort, nsxSubnetPath) + if err != nil { + log.Error(err, "failed to retrieve subnet status for subnetport", "subnetport", subnetPort, "nsxSubnetPath", nsxSubnetPath) + } + if reflect.DeepEqual(old_status, subnetPort.Status) { + log.Info("status (without conditions) already matched", "new status", subnetPort.Status, "existing status", old_status) + } else { + // If the SubnetPort CR's status changed, let's clean the conditions, to ensure the r.Client.Status().Update in the following updateSuccess will be invoked at any time. + subnetPort.Status.Conditions = nil + } + updateSuccess(r, &ctx, subnetPort) + } else { + if controllerutil.ContainsFinalizer(subnetPort, servicecommon.SubnetPortFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResTypeSubnetPort) + if err := r.Service.DeleteSubnetPort(subnetPort.UID); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "subnetport", req.NamespacedName) + deleteFail(r, &ctx, subnetPort, &err) + return common.ResultRequeue, err + } + controllerutil.RemoveFinalizer(subnetPort, servicecommon.SubnetPortFinalizerName) + if err := r.Client.Update(ctx, subnetPort); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "subnetport", req.NamespacedName) + deleteFail(r, &ctx, subnetPort, &err) + return common.ResultRequeue, err + } + log.Info("removed finalizer", "subnetport", req.NamespacedName) + deleteSuccess(r, &ctx, subnetPort) + } else { + log.Info("finalizers cannot be recognized", "subnetport", req.NamespacedName) + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SubnetPortReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.SubnetPort{}). + WithEventFilter( + predicate.Funcs{ + DeleteFunc: func(e event.DeleteEvent) bool { + // Suppress Delete events to avoid filtering them out in the Reconcile function + return false + }, + }, + ). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Watches(&source.Kind{Type: &vmv1alpha1.VirtualMachine{}}, + handler.EnqueueRequestsFromMapFunc(r.vmMapFunc), + builder.WithPredicates(predicate.LabelChangedPredicate{})). + Complete(r) // TODO: watch the virtualmachine event and update the labels on NSX subnet port. +} + +func (r *SubnetPortReconciler) vmMapFunc(vm client.Object) []reconcile.Request { + subnetPortList := &v1alpha1.SubnetPortList{} + var requests []reconcile.Request + err := retry.OnError(retry.DefaultRetry, func(err error) bool { + return err != nil + }, func() error { + err := r.Client.List(context.TODO(), subnetPortList) + return err + }) + if err != nil { + log.Error(err, "failed to list subnetport in VM handler") + return requests + } + for _, subnetPort := range subnetPortList.Items { + if subnetPort.Spec.AttachmentRef.Name == vm.GetName() && (subnetPort.Spec.AttachmentRef.Namespace == vm.GetNamespace() || + (subnetPort.Spec.AttachmentRef.Namespace == "" && subnetPort.Namespace == vm.GetNamespace())) { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: subnetPort.Name, + Namespace: subnetPort.Namespace, + }, + }) + } + } + return requests +} + +func StartSubnetPortController(mgr ctrl.Manager, commonService servicecommon.Service) { + subnetPortReconciler := SubnetPortReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if subnetPortService, err := subnetport.InitializeSubnetPort(commonService); err != nil { + log.Error(err, "failed to initialize subnetport commonService", "controller", "SubnetPort") + os.Exit(1) + } else { + subnetPortReconciler.Service = subnetPortService + common.ServiceMediator.SubnetPortService = subnetPortReconciler.Service + } + if err := subnetPortReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "SubnetPort") + os.Exit(1) + } +} + +// Start setup manager and launch GC +func (r *SubnetPortReconciler) Start(mgr ctrl.Manager) error { + err := r.SetupWithManager(mgr) + if err != nil { + return err + } + go r.GarbageCollector(make(chan bool), servicecommon.GCInterval) + return nil +} + +// GarbageCollector collect SubnetPort which has been removed from crd. +// cancel is used to break the loop during UT +func (r *SubnetPortReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("subnetport garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxSubnetPortSet := r.Service.ListNSXSubnetPortIDForCR() + if len(nsxSubnetPortSet) == 0 { + continue + } + subnetPortList := &v1alpha1.SubnetPortList{} + err := r.Client.List(ctx, subnetPortList) + if err != nil { + log.Error(err, "failed to list SubnetPort CR") + continue + } + + CRSubnetPortSet := sets.NewString() + for _, subnetPort := range subnetPortList.Items { + CRSubnetPortSet.Insert(string(subnetPort.UID)) + } + + for elem := range nsxSubnetPortSet { + if CRSubnetPortSet.Has(elem) { + continue + } + log.V(1).Info("GC collected SubnetPort CR", "UID", elem) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResTypeSubnetPort) + err = r.Service.DeleteSubnetPort(types.UID(elem)) + if err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeSubnetPort) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeSubnetPort) + } + } + } +} + +func (r *SubnetPortReconciler) setSubnetPortReadyStatusTrue(ctx *context.Context, subnetPort *v1alpha1.SubnetPort) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX subnet port has been successfully created/updated", + Reason: "NSX API returned 200 response code for PATCH", + }, + } + r.UpdateSubnetPortStatusConditions(ctx, subnetPort, newConditions) +} + +func (r *SubnetPortReconciler) setSubnetPortReadyStatusFalse(ctx *context.Context, subnetPort *v1alpha1.SubnetPort, err *error) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX subnet port could not be created/updated", + Reason: fmt.Sprintf( + "error occurred while processing the SubnetPort CR. Error: %v", + *err, + ), + }, + } + r.UpdateSubnetPortStatusConditions(ctx, subnetPort, newConditions) +} + +func (r *SubnetPortReconciler) UpdateSubnetPortStatusConditions(ctx *context.Context, subnetPort *v1alpha1.SubnetPort, newConditions []v1alpha1.Condition) { + conditionsUpdated := false + for i := range newConditions { + if r.mergeSubnetPortStatusCondition(ctx, subnetPort, &newConditions[i]) { + conditionsUpdated = true + } + } + if conditionsUpdated { + r.Client.Status().Update(*ctx, subnetPort) + log.V(1).Info("updated subnet port CR", "Name", subnetPort.Name, "Namespace", subnetPort.Namespace, + "New Conditions", newConditions) + } +} + +func (r *SubnetPortReconciler) mergeSubnetPortStatusCondition(ctx *context.Context, subnetPort *v1alpha1.SubnetPort, newCondition *v1alpha1.Condition) bool { + matchedCondition := getExistingConditionOfType(newCondition.Type, subnetPort.Status.Conditions) + + if reflect.DeepEqual(matchedCondition, newCondition) { + log.V(2).Info("conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition) + return false + } + + if matchedCondition != nil { + matchedCondition.Reason = newCondition.Reason + matchedCondition.Message = newCondition.Message + matchedCondition.Status = newCondition.Status + } else { + subnetPort.Status.Conditions = append(subnetPort.Status.Conditions, *newCondition) + } + return true +} + +func getExistingConditionOfType(conditionType v1alpha1.ConditionType, existingConditions []v1alpha1.Condition) *v1alpha1.Condition { + for i := range existingConditions { + if existingConditions[i].Type == conditionType { + return &existingConditions[i] + } + } + return nil +} + +func updateFail(r *SubnetPortReconciler, c *context.Context, o *v1alpha1.SubnetPort, e *error) { + r.setSubnetPortReadyStatusFalse(c, o, e) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResTypeSubnetPort) +} + +func deleteFail(r *SubnetPortReconciler, c *context.Context, o *v1alpha1.SubnetPort, e *error) { + r.setSubnetPortReadyStatusFalse(c, o, e) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeSubnetPort) +} + +func updateSuccess(r *SubnetPortReconciler, c *context.Context, o *v1alpha1.SubnetPort) { + r.setSubnetPortReadyStatusTrue(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResTypeSubnetPort) +} + +func deleteSuccess(r *SubnetPortReconciler, _ *context.Context, _ *v1alpha1.SubnetPort) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeSubnetPort) +} + +func (r *SubnetPortReconciler) GetSubnetPathForSubnetPort(ctx context.Context, subnetPort *v1alpha1.SubnetPort) (string, error) { + subnetPath := r.Service.GetSubnetPathForSubnetPortFromStore(string(subnetPort.UID)) + if len(subnetPath) > 0 { + log.V(1).Info("NSX subnet port had been created, returning the existing NSX subnet path", "subnetPort.UID", subnetPort.UID, "subnetPath", subnetPath) + return subnetPath, nil + } + if len(subnetPort.Spec.Subnet) > 0 { + subnet := &v1alpha1.Subnet{} + namespacedName := types.NamespacedName{ + Name: subnetPort.Spec.Subnet, + Namespace: subnetPort.Namespace, + } + if err := r.Client.Get(ctx, namespacedName, subnet); err != nil { + log.Error(err, "subnet CR not found", "subnet CR", namespacedName) + return subnetPath, err + } + subnetPath = subnet.Status.NSXResourcePath + if len(subnetPath) == 0 { + err := fmt.Errorf("empty NSX resource path from subnet %s", subnet.Name) + return subnetPath, err + } + } else if len(subnetPort.Spec.SubnetSet) > 0 { + subnetSet := &v1alpha1.SubnetSet{} + namespacedName := types.NamespacedName{ + Name: subnetPort.Spec.SubnetSet, + Namespace: subnetPort.Namespace, + } + if err := r.Client.Get(context.Background(), namespacedName, subnetSet); err != nil { + log.Error(err, "subnetSet CR not found", "subnet CR", namespacedName) + return subnetPath, err + } + log.Info("got subnetset for subnetport CR, allocating the NSX subnet", "subnetSet.Name", subnetSet.Name, "subnetSet.UID", subnetSet.UID, "subnetPort.Name", subnetPort.Name, "subnetPort.UID", subnetPort.UID) + subnetPath, err := common.AllocateSubnetFromSubnetSet(subnetSet) + log.Info("allocated Subnet for SubnetPort", "subnetPath", subnetPath, "subnetPort.Name", subnetPort.Name, "subnetPort.UID", subnetPort.UID) + if err != nil { + return subnetPath, err + } + return subnetPath, nil + } else { + subnetSet, err := common.GetDefaultSubnetSet(r.Client, ctx, subnetPort.Namespace, servicecommon.LabelDefaultVMSubnetSet) + if err != nil { + return "", err + } + log.Info("got default subnetset for subnetport CR, allocating the NSX subnet", "subnetSet.Name", subnetSet.Name, "subnetSet.UID", subnetSet.UID, "subnetPort.Name", subnetPort.Name, "subnetPort.UID", subnetPort.UID) + subnetPath, err := common.AllocateSubnetFromSubnetSet(subnetSet) + log.Info("allocated Subnet for SubnetPort", "subnetPath", subnetPath, "subnetPort.Name", subnetPort.Name, "subnetPort.UID", subnetPort.UID) + if err != nil { + return subnetPath, err + } + return subnetPath, nil + } + return subnetPath, nil +} + +func (r *SubnetPortReconciler) updateSubnetStatusOnSubnetPort(subnetPort *v1alpha1.SubnetPort, nsxSubnetPath string) error { + gateway, netmask, err := r.Service.GetGatewayNetmaskForSubnetPort(subnetPort, nsxSubnetPath) + if err != nil { + return err + } + subnetInfo, err := servicecommon.ParseVPCResourcePath(nsxSubnetPath) + if err != nil { + return err + } + // For now, we have an asumption that one subnetport only have one IP address + subnetPort.Status.IPAddresses[0].Gateway = gateway + subnetPort.Status.IPAddresses[0].Netmask = netmask + nsxSubnet := common.ServiceMediator.SubnetStore.GetByKey(subnetInfo.ID) + if nsxSubnet == nil { + return errors.New("NSX subnet not found in store") + } + subnetPort.Status.LogicalSwitchID = *nsxSubnet.RealizationId + return nil +} + +func (r *SubnetPortReconciler) getLabelsFromVirtualMachine(ctx context.Context, subnetPort *v1alpha1.SubnetPort) (*map[string]string, error) { + if subnetPort.Spec.AttachmentRef.Name == "" { + return nil, nil + } + vm := &vmv1alpha1.VirtualMachine{} + namespace := subnetPort.Spec.AttachmentRef.Namespace + if len(namespace) == 0 { + namespace = subnetPort.Namespace + } + namespacedName := types.NamespacedName{ + Name: subnetPort.Spec.AttachmentRef.Name, + Namespace: namespace, + } + if err := r.Client.Get(ctx, namespacedName, vm); err != nil { + return nil, err + } + return &vm.ObjectMeta.Labels, nil +} diff --git a/pkg/controllers/subnetport/subnetport_controller_test.go b/pkg/controllers/subnetport/subnetport_controller_test.go new file mode 100644 index 000000000..8571ca4ab --- /dev/null +++ b/pkg/controllers/subnetport/subnetport_controller_test.go @@ -0,0 +1,294 @@ +package subnetport + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/agiledragon/gomonkey" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + mock_client "github.com/vmware-tanzu/nsx-operator/pkg/mock/controller-runtime/client" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetport" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func NewFakeSubnetPortReconciler() *SubnetPortReconciler { + return &SubnetPortReconciler{ + Client: fake.NewClientBuilder().Build(), + Scheme: fake.NewClientBuilder().Build().Scheme(), + Service: nil, + } +} + +func TestSubnetPortReconciler_Reconcile(t *testing.T) { + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + service := &subnetport.SubnetPortService{ + Service: common.Service{ + NSXClient: &nsx.Client{}, + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + r := &SubnetPortReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + ctx := context.Background() + req := controllerruntime.Request{NamespacedName: types.NamespacedName{Namespace: "dummy", Name: "dummy"}} + + // not found + errNotFound := errors.New("not found") + k8sClient.EXPECT().Get(ctx, gomock.Any(), gomock.Any()).Return(errNotFound) + _, err := r.Reconcile(ctx, req) + assert.Equal(t, err, errNotFound) + + // update fails + sp := &v1alpha1.SubnetPort{} + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + return nil + }) + err = errors.New("Update failed") + k8sClient.EXPECT().Update(ctx, gomock.Any()).Return(err) + patchesSuccess := gomonkey.ApplyFunc(updateSuccess, + func(r *SubnetPortReconciler, c *context.Context, o *v1alpha1.SubnetPort) { + }) + defer patchesSuccess.Reset() + patchesUpdateFail := gomonkey.ApplyFunc(updateFail, + func(r *SubnetPortReconciler, c *context.Context, o *v1alpha1.SubnetPort, e *error) { + }) + defer patchesUpdateFail.Reset() + _, ret := r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // both subnet and subnetset are configured + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + v1sp.Spec.SubnetSet = "subnetset2" + return nil + }) + err = errors.New("subnet and subnetset should not be configured at the same time") + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // CreateOrUpdateSubnetPort fails + patchesGetLabelsFromVirtualMachine := gomonkey.ApplyFunc((*SubnetPortReconciler).getLabelsFromVirtualMachine, + func(r *SubnetPortReconciler, ctx context.Context, obj *v1alpha1.SubnetPort) (*map[string]string, error) { + return nil, nil + }) + defer patchesGetLabelsFromVirtualMachine.Reset() + patchesVmMapFunc := gomonkey.ApplyFunc((*SubnetPortReconciler).vmMapFunc, + func(r *SubnetPortReconciler, vm client.Object) []reconcile.Request { + requests := []reconcile.Request{} + return requests + }) + defer patchesVmMapFunc.Reset() + k8sClient.EXPECT().Update(ctx, gomock.Any()).Return(nil) + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + return nil + }) + err = errors.New("CreateOrUpdateSubnetPort failed") + patchesGetSubnetPathForSubnetPort := gomonkey.ApplyFunc((*SubnetPortReconciler).GetSubnetPathForSubnetPort, + func(r *SubnetPortReconciler, ctx context.Context, obj *v1alpha1.SubnetPort) (string, error) { + return "", nil + }) + defer patchesGetSubnetPathForSubnetPort.Reset() + patchesCreateOrUpdateSubnetPort := gomonkey.ApplyFunc((*subnetport.SubnetPortService).CreateOrUpdateSubnetPort, + func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnetPath string, contextID string, tags *map[string]string) (*model.SegmentPortState, error) { + return nil, err + }) + defer patchesCreateOrUpdateSubnetPort.Reset() + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // happy path + k8sClient.EXPECT().Update(ctx, gomock.Any()).Return(nil) + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + return nil + }) + portIP := "1.2.3.4" + portMac := "aa:bb:cc:dd" + attachmentID := "attachment-id" + portState := &model.SegmentPortState{ + RealizedBindings: []model.AddressBindingEntry{ + { + Binding: &model.PacketAddressClassifier{ + IpAddress: &portIP, + MacAddress: &portMac, + }, + }, + }, + Attachment: &model.SegmentPortAttachmentState{ + Id: &attachmentID, + }, + } + patchesCreateOrUpdateSubnetPort = gomonkey.ApplyFunc((*subnetport.SubnetPortService).CreateOrUpdateSubnetPort, + func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnetPath string, contextID string, tags *map[string]string) (*model.SegmentPortState, error) { + return portState, nil + }) + defer patchesCreateOrUpdateSubnetPort.Reset() + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, nil, ret) + + // handle deletion event - delete NSX subnet port failed + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Finalizers = []string{common.SubnetPortFinalizerName} + return nil + }) + err = errors.New("DeleteSubnetPort failed") + patchesDeleteSubnetPort := gomonkey.ApplyFunc((*subnetport.SubnetPortService).DeleteSubnetPort, + func(s *subnetport.SubnetPortService, uid types.UID) error { + return err + }) + defer patchesDeleteSubnetPort.Reset() + patchesCreateOrUpdateSubnetPort = gomonkey.ApplyFunc((*subnetport.SubnetPortService).CreateOrUpdateSubnetPort, + func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnetPath string, contextID string, tags *map[string]string) (*model.SegmentPortState, error) { + assert.FailNow(t, "should not be called") + return nil, nil + }) + defer patchesCreateOrUpdateSubnetPort.Reset() + patchesDeleteFail := gomonkey.ApplyFunc(deleteFail, + func(r *SubnetPortReconciler, c *context.Context, o *v1alpha1.SubnetPort, e *error) { + }) + defer patchesDeleteFail.Reset() + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // handle deletion event - update subnetport failed in deletion event + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Finalizers = []string{common.SubnetPortFinalizerName} + return nil + }) + err = errors.New("Update failed") + k8sClient.EXPECT().Update(ctx, gomock.Any()).Return(err) + patchesDeleteSubnetPort = gomonkey.ApplyFunc((*subnetport.SubnetPortService).DeleteSubnetPort, + func(s *subnetport.SubnetPortService, uid types.UID) error { + return nil + }) + defer patchesDeleteSubnetPort.Reset() + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, err, ret) + + // handle deletion event - successfully deleted + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + v1sp.Finalizers = []string{common.SubnetPortFinalizerName} + return nil + }) + k8sClient.EXPECT().Update(ctx, gomock.Any()).Return(nil) + patchesDeleteSubnetPort = gomonkey.ApplyFunc((*subnetport.SubnetPortService).DeleteSubnetPort, + func(s *subnetport.SubnetPortService, uid types.UID) error { + return nil + }) + defer patchesDeleteSubnetPort.Reset() + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, nil, ret) + + // handle deletion event - unknown finalizers + k8sClient.EXPECT().Get(ctx, gomock.Any(), sp).Return(nil).Do( + func(_ context.Context, _ client.ObjectKey, obj client.Object, option ...client.GetOption) error { + v1sp := obj.(*v1alpha1.SubnetPort) + v1sp.Spec.Subnet = "subnet1" + time := metav1.Now() + v1sp.ObjectMeta.DeletionTimestamp = &time + return nil + }) + patchesDeleteSubnetPort = gomonkey.ApplyFunc((*subnetport.SubnetPortService).DeleteSubnetPort, + func(s *subnetport.SubnetPortService, uid types.UID) error { + assert.FailNow(t, "should not be called") + return nil + }) + defer patchesDeleteSubnetPort.Reset() + _, ret = r.Reconcile(ctx, req) + assert.Equal(t, nil, ret) +} + +func TestSubnetPortReconciler_GarbageCollector(t *testing.T) { + // gc collect item "2345", local store has more item than k8s cache + service := &subnetport.SubnetPortService{ + Service: common.Service{ + NSXConfig: &config.NSXOperatorConfig{ + NsxConfig: &config.NsxConfig{ + EnforcementPoint: "vmc-enforcementpoint", + }, + }, + }, + } + patchesListNSXSubnetPortIDForCR := gomonkey.ApplyFunc((*subnetport.SubnetPortService).ListNSXSubnetPortIDForCR, + func(s *subnetport.SubnetPortService) sets.String { + a := sets.NewString() + a.Insert("1234") + a.Insert("2345") + return a + }) + defer patchesListNSXSubnetPortIDForCR.Reset() + patchesDeleteSubnetPort := gomonkey.ApplyFunc((*subnetport.SubnetPortService).DeleteSubnetPort, + func(s *subnetport.SubnetPortService, uid types.UID) error { + return nil + }) + defer patchesDeleteSubnetPort.Reset() + cancel := make(chan bool) + mockCtl := gomock.NewController(t) + k8sClient := mock_client.NewMockClient(mockCtl) + r := &SubnetPortReconciler{ + Client: k8sClient, + Scheme: nil, + Service: service, + } + subnetPortList := &v1alpha1.SubnetPortList{} + k8sClient.EXPECT().List(gomock.Any(), subnetPortList).Return(nil).Do(func(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + a := list.(*v1alpha1.SubnetPortList) + a.Items = append(a.Items, v1alpha1.SubnetPort{}) + a.Items[0].ObjectMeta = metav1.ObjectMeta{} + a.Items[0].UID = "1234" + return nil + }) + go func() { + time.Sleep(1 * time.Second) + cancel <- true + }() + r.GarbageCollector(cancel, time.Second) +} diff --git a/pkg/controllers/subnetset/namespace_handler.go b/pkg/controllers/subnetset/namespace_handler.go new file mode 100644 index 000000000..c38336609 --- /dev/null +++ b/pkg/controllers/subnetset/namespace_handler.go @@ -0,0 +1,86 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package subnetset + +import ( + "context" + "reflect" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" +) + +// SubnetSet controller should watch event of namespace, when there are some updates of namespace labels, +// controller should build tags and update VpcSubnetSetSet according to new labels. + +type EnqueueRequestForNamespace struct { + Client client.Client +} + +func (e *EnqueueRequestForNamespace) Create(_ event.CreateEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("namespace create event, do nothing") +} + +func (e *EnqueueRequestForNamespace) Delete(_ event.DeleteEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("namespace delete event, do nothing") +} + +func (e *EnqueueRequestForNamespace) Generic(_ event.GenericEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("namespace generic event, do nothing") +} + +func (e *EnqueueRequestForNamespace) Update(updateEvent event.UpdateEvent, l workqueue.RateLimitingInterface) { + obj := updateEvent.ObjectNew.(*v1.Namespace) + err := reconcileSubnetSet(e.Client, obj.Name, l) + if err != nil { + log.Error(err, "failed to reconcile subnet") + } +} + +var PredicateFuncsNs = predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObj := e.ObjectOld.(*v1.Namespace) + newObj := e.ObjectNew.(*v1.Namespace) + log.V(1).Info("receive namespace update event", "name", oldObj.Name) + if reflect.DeepEqual(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) { + log.Info("label of namespace is not changed, ignore it", "name", oldObj.Name) + return false + } + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, +} + +func reconcileSubnetSet(c client.Client, namespace string, q workqueue.RateLimitingInterface) error { + subnetSetList := &v1alpha1.SubnetSetList{} + err := c.List(context.Background(), subnetSetList, client.InNamespace(namespace)) + if err != nil { + log.Error(err, "failed to list all the subnets") + return err + } + + for _, subnet_set_item := range subnetSetList.Items { + log.Info("reconcile subnet set because namespace update", + "namespace", subnet_set_item.Namespace, "name", subnet_set_item.Name) + q.Add(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: subnet_set_item.Name, + Namespace: subnet_set_item.Namespace, + }, + }) + } + return nil +} diff --git a/pkg/controllers/subnetset/subnetport_handler.go b/pkg/controllers/subnetset/subnetport_handler.go new file mode 100644 index 000000000..a0d545545 --- /dev/null +++ b/pkg/controllers/subnetset/subnetport_handler.go @@ -0,0 +1,88 @@ +package subnetset + +import ( + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +// SubnetPortHandler supports lazy-creation of Subnet, the first Subnet won't +// be created until there is a SubnetPort attached to it. +// - SubnetPort creation: get available Subnet for the SubnetPort, create new +// Subnet if necessary. +// - SubnetPort deletion: if recycling Subnet is required, delete Subnets without +// SubnetPort attached to it. + +type SubnetPortHandler struct { + Reconciler *SubnetSetReconciler +} + +//TODO Remove this handler when confirmed that SubnetPort could get allocated subnet via the interface +// subnetservice.GetAvailableSubnet + +// Create allocates Subnet for SubnetPort from SubnetSet. +func (h *SubnetPortHandler) Create(e event.CreateEvent, _ workqueue.RateLimitingInterface) { + log.V(4).Info("SubnetPort generic event, do nothing") + //subnetPort := e.Object.(*v1alpha1.SubnetPort) + //if subnetPort.Spec.Subnet != "" { + // // Two possible scenarios: + // // - 1. User uses `.Spec.Subnet` directly instead of `.Spec.SubnetSet`. + // // - 2. Subnet has been allocated and `.Spec.Subnet` is rendered by SubnetPortHandler. + // return + //} + //subnetSet := &v1alpha1.SubnetSet{} + //key := types.NamespacedName{ + // Namespace: subnetPort.GetNamespace(), + // Name: subnetPort.Spec.SubnetSet, + //} + //if err := h.Reconciler.Client.Get(context.Background(), key, subnetSet); err != nil { + // log.Error(err, "failed to get SubnetSet", "ns", key.Namespace, "name", key.Name) + // return + //} + //log.Info("allocating Subnet for SubnetPort") + //vpcList := &v1alpha1.VPCList{} + //if err := h.Reconciler.Client.List(context.Background(), vpcList, client.InNamespace(subnetPort.GetNamespace())); err != nil { + // log.Error(err, fmt.Sprintf("failed to get VPC under namespace: %s.\n", subnetPort.GetNamespace())) + // return + //} + //vpcInfo, err := servicecommon.ParseVPCResourcePath(vpcList.Items[0].Status.NSXResourcePath) + //if err != nil { + // log.Error(err, "failed to resolve VPC info") + // return + //} + //_, err = h.Reconciler.getAvailableSubnet(subnetSet, &vpcInfo) + //if err != nil { + // log.Error(err, "failed to allocate Subnet") + //} + // TODO return subnetport id to caller. +} + +// Delete TODO Implement this method if required to recycle Subnet without SubnetPort attached. +func (h *SubnetPortHandler) Delete(e event.DeleteEvent, _ workqueue.RateLimitingInterface) { + log.V(4).Info("SubnetPort generic event, do nothing") +} + +func (h *SubnetPortHandler) Generic(_ event.GenericEvent, _ workqueue.RateLimitingInterface) { + log.V(4).Info("SubnetPort generic event, do nothing") +} + +func (h *SubnetPortHandler) Update(_ event.UpdateEvent, _ workqueue.RateLimitingInterface) { + log.V(4).Info("SubnetPort update event, do nothing") +} + +var SubnetPortPredicate = predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // TODO When recycling Subnet is required, return true. + return false + }, + GenericFunc: func(genericEvent event.GenericEvent) bool { + return false + }, +} diff --git a/pkg/controllers/subnetset/subnetset_controller.go b/pkg/controllers/subnetset/subnetset_controller.go new file mode 100644 index 000000000..209fc63fc --- /dev/null +++ b/pkg/controllers/subnetset/subnetset_controller.go @@ -0,0 +1,323 @@ +package subnetset + +import ( + "context" + "errors" + "fmt" + "reflect" + "runtime" + "time" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + v1 "k8s.io/api/core/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet" +) + +var ( + log = logger.Log + ResultNormal = common.ResultNormal + ResultRequeue = common.ResultRequeue + ResultRequeueAfter5mins = common.ResultRequeueAfter5mins + MetricResTypeSubnetSet = common.MetricResTypeSubnetSet + //TODO rename this + defaultSubnet = "default-subnet" +) + +// SubnetSetReconciler reconciles a SubnetSet object +type SubnetSetReconciler struct { + Client client.Client + Scheme *apimachineryruntime.Scheme + Service *subnet.SubnetService +} + +func (r *SubnetSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + obj := &v1alpha1.SubnetSet{} + log.Info("reconciling subnetset CR", "subnetset", req.NamespacedName) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, MetricResTypeSubnetSet) + + if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil { + log.Error(err, "unable to fetch subnetset CR", "req", req.NamespacedName) + return ResultNormal, client.IgnoreNotFound(err) + } + + if obj.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, MetricResTypeSubnetSet) + if !controllerutil.ContainsFinalizer(obj, servicecommon.SubnetSetFinalizerName) { + controllerutil.AddFinalizer(obj, servicecommon.SubnetSetFinalizerName) + if obj.Spec.AccessMode == "" || obj.Spec.IPv4SubnetSize == 0 { + vpcNetworkConfig := commonctl.ServiceMediator.GetVPCNetworkConfigByNamespace(obj.Namespace) + if vpcNetworkConfig == nil { + err := fmt.Errorf("failed to find VPCNetworkConfig for namespace %s", obj.Namespace) + log.Error(err, "operate failed, would retry exponentially", "subnet", req.NamespacedName) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + if obj.Spec.AccessMode == "" { + obj.Spec.AccessMode = v1alpha1.AccessMode(vpcNetworkConfig.DefaultSubnetAccessMode) + } + if obj.Spec.IPv4SubnetSize == 0 { + obj.Spec.IPv4SubnetSize = vpcNetworkConfig.DefaultIPv4SubnetSize + } + } + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "add finalizer", "subnetset", req.NamespacedName) + updateFail(r, &ctx, obj) + return ResultRequeue, err + } + log.V(1).Info("added finalizer on subnetset CR", "subnetset", req.NamespacedName) + } + + // update subnetset tags if labels of namespace changed + nsxSubnets := r.Service.SubnetStore.GetByIndex(servicecommon.TagScopeSubnetSetCRUID, string(obj.UID)) + if len(nsxSubnets) > 0 { + nsObj := &v1.Namespace{} + if err := r.Client.Get(ctx, client.ObjectKey{Name: obj.Namespace}, nsObj); err != nil { + err = fmt.Errorf("unable to fetch namespace %s", obj.Namespace) + log.Error(err, "") + return ResultRequeue, err + } + tags := r.Service.GenerateSubnetNSTags(obj, string(nsObj.UID)) + for k, v := range nsObj.Labels { + tags = append(tags, model.Tag{Scope: servicecommon.String(k), Tag: servicecommon.String(v)}) + } + if err := r.Service.UpdateSubnetSetTags(obj.Namespace, nsxSubnets, tags); err != nil { + log.Error(err, "failed to update subnetset tags") + } + } + updateSuccess(r, &ctx, obj) + } else { + if controllerutil.ContainsFinalizer(obj, servicecommon.SubnetSetFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, MetricResTypeSubnetSet) + if err := r.DeleteSubnetForSubnetSet(*obj, false); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "subnetset", req.NamespacedName) + deleteFail(r, &ctx, obj) + return ResultRequeue, err + } + controllerutil.RemoveFinalizer(obj, servicecommon.SubnetSetFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "deletion failed, would retry exponentially", "subnetset", req.NamespacedName) + deleteFail(r, &ctx, obj) + return ResultRequeue, err + } + log.V(1).Info("removed finalizer", "subnetset", req.NamespacedName) + deleteSuccess(r, &ctx, obj) + } else { + log.Info("finalizers cannot be recognized", "subnetset", req.NamespacedName) + } + } + return ctrl.Result{}, nil +} + +func updateFail(r *SubnetSetReconciler, c *context.Context, o *v1alpha1.SubnetSet) { + r.setSubnetSetReadyStatusFalse(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateFailTotal, MetricResTypeSubnetSet) +} + +func deleteFail(r *SubnetSetReconciler, c *context.Context, o *v1alpha1.SubnetSet) { + r.setSubnetSetReadyStatusFalse(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeSubnetSet) +} + +func updateSuccess(r *SubnetSetReconciler, c *context.Context, o *v1alpha1.SubnetSet) { + r.setSubnetSetReadyStatusTrue(c, o) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateSuccessTotal, MetricResTypeSubnetSet) +} + +func deleteSuccess(r *SubnetSetReconciler, _ *context.Context, _ *v1alpha1.SubnetSet) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeSubnetSet) +} + +func (r *SubnetSetReconciler) setSubnetSetReadyStatusTrue(ctx *context.Context, subnetset *v1alpha1.SubnetSet) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX SubnetSet has been successfully created/updated", + Reason: "SubnetsReady", + }, + } + r.updateSubnetSetStatusConditions(ctx, subnetset, newConditions) +} + +func (r *SubnetSetReconciler) setSubnetSetReadyStatusFalse(ctx *context.Context, subnetset *v1alpha1.SubnetSet) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX SubnetSet could not be created/updated", + Reason: "SubnetNotReady", + }, + } + r.updateSubnetSetStatusConditions(ctx, subnetset, newConditions) +} + +func (r *SubnetSetReconciler) updateSubnetSetStatusConditions(ctx *context.Context, subnetset *v1alpha1.SubnetSet, newConditions []v1alpha1.Condition) { + conditionsUpdated := false + for i := range newConditions { + if r.mergeSubnetSetStatusCondition(ctx, subnetset, &newConditions[i]) { + conditionsUpdated = true + } + } + if conditionsUpdated { + r.Client.Status().Update(*ctx, subnetset) + log.V(1).Info("updated Subnet", "Name", subnetset.Name, "Namespace", subnetset.Namespace, + "New Conditions", newConditions) + } +} + +func (r *SubnetSetReconciler) mergeSubnetSetStatusCondition(ctx *context.Context, subnetset *v1alpha1.SubnetSet, newCondition *v1alpha1.Condition) bool { + matchedCondition := getExistingConditionOfType(newCondition.Type, subnetset.Status.Conditions) + + if reflect.DeepEqual(matchedCondition, newCondition) { + log.V(2).Info("conditions already match", "New Condition", newCondition, "Existing Condition", matchedCondition) + return false + } + + if matchedCondition != nil { + matchedCondition.Reason = newCondition.Reason + matchedCondition.Message = newCondition.Message + matchedCondition.Status = newCondition.Status + } else { + subnetset.Status.Conditions = append(subnetset.Status.Conditions, *newCondition) + } + return true +} + +func getExistingConditionOfType(conditionType v1alpha1.ConditionType, existingConditions []v1alpha1.Condition) *v1alpha1.Condition { + for i := range existingConditions { + if existingConditions[i].Type == conditionType { + return &existingConditions[i] + } + } + return nil +} + +func (r *SubnetSetReconciler) setupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.SubnetSet{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Watches(&source.Kind{Type: &v1alpha1.VPC{}}, + &VPCHandler{Client: mgr.GetClient()}, + builder.WithPredicates(VPCPredicate)). + Watches( + &source.Kind{Type: &v1alpha1.SubnetPort{}}, + &SubnetPortHandler{Reconciler: r}, + builder.WithPredicates(SubnetPortPredicate)). + Watches( + &source.Kind{Type: &v1.Namespace{}}, + &EnqueueRequestForNamespace{Client: mgr.GetClient()}, + builder.WithPredicates(PredicateFuncsNs), + ). + Complete(r) +} + +// GarbageCollector collect Subnet which there is no port attached on it. +// cancel is used to break the loop during UT +func (r *SubnetSetReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("subnetset garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + + subnetSetList := &v1alpha1.SubnetSetList{} + err := r.Client.List(ctx, subnetSetList) + if err != nil { + log.Error(err, "failed to list SubnetSet CR") + continue + } + + nsxSubnetList := r.Service.ListSubnetCreatedBySubnetSet() + if len(nsxSubnetList) == 0 { + continue + } + + subnetSetIDs := sets.NewString() + for _, subnetSet := range subnetSetList.Items { + if err := r.DeleteSubnetForSubnetSet(subnetSet, true); err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeSubnetSet) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeSubnetSet) + } + subnetSetIDs.Insert(string(subnetSet.UID)) + } + for _, subnet := range nsxSubnetList { + if !r.Service.IsOrphanSubnet(subnet, subnetSetIDs) { + continue + } + if err := r.Service.DeleteSubnet(subnet); err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, MetricResTypeSubnetSet) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, MetricResTypeSubnetSet) + } + } + } +} + +func (r *SubnetSetReconciler) DeleteSubnetForSubnetSet(obj v1alpha1.SubnetSet, updataStatus bool) error { + nsxSubnets := r.Service.SubnetStore.GetByIndex(servicecommon.TagScopeSubnetSetCRUID, string(obj.GetUID())) + hitError := false + for _, subnet := range nsxSubnets { + portNums := len(common.ServiceMediator.GetPortsOfSubnet(*subnet.Id)) + if portNums > 0 { + continue + } + if err := r.Service.DeleteSubnet(subnet); err != nil { + log.Error(err, "fail to delete subnet from subnetset cr", "ID", *subnet.Id) + hitError = true + } + + } + if updataStatus { + if err := r.Service.UpdateSubnetSetStatus(&obj); err != nil { + return err + } + } + if hitError { + return errors.New("error occurs when deleting subnet") + } + return nil +} + +func StartSubnetSetController(mgr ctrl.Manager, commonService servicecommon.Service) error { + subnetsetReconciler := &SubnetSetReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + subnetsetReconciler.Service = subnet.GetSubnetService(commonService) + if err := subnetsetReconciler.Start(mgr); err != nil { + log.Error(err, "failed to create controller", "controller", "Subnet") + return err + } + return nil +} + +// Start setup manager +func (r *SubnetSetReconciler) Start(mgr ctrl.Manager) error { + err := r.setupWithManager(mgr) + if err != nil { + return err + } + go r.GarbageCollector(make(chan bool), servicecommon.GCInterval) + return nil +} diff --git a/pkg/controllers/subnetset/vpc_handler.go b/pkg/controllers/subnetset/vpc_handler.go new file mode 100644 index 000000000..0c7cebe8f --- /dev/null +++ b/pkg/controllers/subnetset/vpc_handler.go @@ -0,0 +1,120 @@ +package subnetset + +import ( + "context" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + + "sigs.k8s.io/controller-runtime/pkg/predicate" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/retry" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" +) + +// VPCHandler handles VPC event for SubnetSet: +// - VPC creation: create default SubnetSet for the VPC. +// - VPC deletion: delete all SubnetSets under the VPC. + +var defaultSubnetSets = map[string]string{ + "default-vm-subnetset": common.LabelDefaultVMSubnetSet, + "default-pod-subnetset": common.LabelDefaultPodSubnetSet, +} + +type VPCHandler struct { + Client client.Client +} + +func (h *VPCHandler) Create(e event.CreateEvent, _ workqueue.RateLimitingInterface) { + ns := e.Object.GetNamespace() + log.Info("creating default Subnetset for VPC", "Namespace", ns, "Name", e.Object.GetName()) + for name, subnetSetType := range defaultSubnetSets { + if err := retry.OnError(retry.DefaultRetry, func(err error) bool { + return err != nil + }, func() error { + list := &v1alpha1.SubnetSetList{} + label := client.MatchingLabels{ + common.LabelDefaultSubnetSet: subnetSetType, + } + nsOption := client.InNamespace(ns) + if err := h.Client.List(context.Background(), list, label, nsOption); err != nil { + return err + } + if len(list.Items) > 0 { + // avoid creating when nsx-operator restarted if Subnetset exists. + log.Info("default subnetset already exists", common.LabelDefaultSubnetSet, subnetSetType) + return nil + } + obj := &v1alpha1.SubnetSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + Labels: map[string]string{ + common.LabelDefaultSubnetSet: subnetSetType, + }, + }, + Spec: v1alpha1.SubnetSetSpec{ + AdvancedConfig: v1alpha1.AdvancedConfig{ + StaticIPAllocation: v1alpha1.StaticIPAllocation{ + Enable: true, + }, + }, + }, + } + if err := h.Client.Create(context.Background(), obj); err != nil { + return err + } + return nil + }); err != nil { + log.Error(err, "failed to create SubnetSet", "Namespace", ns, "Name", name) + } + } +} + +func (h *VPCHandler) Delete(e event.DeleteEvent, _ workqueue.RateLimitingInterface) { + log.Info("cleaning default Subnetset for VPC", "Name", e.Object.GetName()) + for _, subnetSetType := range defaultSubnetSets { + if err := retry.OnError(retry.DefaultRetry, func(err error) bool { + return err != nil + }, func() error { + label := client.MatchingLabels{ + common.LabelDefaultSubnetSet: subnetSetType, + } + nsOption := client.InNamespace(e.Object.GetNamespace()) + obj := &v1alpha1.SubnetSet{} + if err := h.Client.DeleteAllOf(context.Background(), obj, label, nsOption); err != nil { + return client.IgnoreNotFound(err) + } + return nil + }); err != nil { + log.Error(err, "failed to delete SubnetSet", common.LabelDefaultSubnetSet, subnetSetType) + } + } +} + +func (h *VPCHandler) Generic(_ event.GenericEvent, _ workqueue.RateLimitingInterface) { + log.V(4).Info("VPC generic event, do nothing") +} + +func (h *VPCHandler) Update(_ event.UpdateEvent, _ workqueue.RateLimitingInterface) { + log.V(4).Info("VPC update event, do nothing") +} + +var VPCPredicate = predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return true + }, + GenericFunc: func(genericEvent event.GenericEvent) bool { + return false + }, +} diff --git a/pkg/controllers/vpc/vpc_controller.go b/pkg/controllers/vpc/vpc_controller.go new file mode 100644 index 000000000..b8e5d0afc --- /dev/null +++ b/pkg/controllers/vpc/vpc_controller.go @@ -0,0 +1,228 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package vpc + +import ( + "context" + "os" + "runtime" + "time" + + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + _ "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + commonservice "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +var ( + log = logger.Log + ResultNormal = common.ResultNormal + ResultRequeue = common.ResultRequeue + ResultRequeueAfter5mins = common.ResultRequeueAfter5mins + MetricResType = common.MetricResTypeVPC +) + +// VPCReconciler VPCReconcile reconciles a VPC object +type VPCReconciler struct { + Client client.Client + Scheme *apimachineryruntime.Scheme + Service *vpc.VPCService +} + +func (r *VPCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + obj := &v1alpha1.VPC{} + log.Info("reconciling VPC CR", "VPC", req.NamespacedName) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerSyncTotal, common.MetricResTypeVPC) + + if err := r.Client.Get(ctx, req.NamespacedName, obj); err != nil { + log.Error(err, "unable to fetch VPC CR", "req", req.NamespacedName) + return common.ResultNormal, client.IgnoreNotFound(err) + } + + if obj.ObjectMeta.DeletionTimestamp.IsZero() { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerUpdateTotal, common.MetricResTypeVPC) + if !controllerutil.ContainsFinalizer(obj, commonservice.VPCFinalizerName) { + controllerutil.AddFinalizer(obj, commonservice.VPCFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + log.Error(err, "add finalizer", "VPC", req.NamespacedName) + updateFail(r.Service.NSXConfig, &ctx, obj, &err, r.Client) + return common.ResultRequeue, err + } + log.V(1).Info("added finalizer on VPC CR", "VPC", req.NamespacedName) + } + + createdVpc, nc, err := r.Service.CreateorUpdateVPC(obj) + if err != nil { + log.Error(err, "operate failed, would retry exponentially", "VPC", req.NamespacedName) + updateFail(r.Service.NSXConfig, &ctx, obj, &err, r.Client) + return common.ResultRequeueAfter10sec, err + } + + snatIP, path, cidr := "", "", "" + // currently, auto snat is not exposed, and use default value True + // checking autosnat to support future extension in vpc configuration + if *createdVpc.ServiceGateway.AutoSnat { + snatIP, err = r.Service.GetDefaultSNATIP(*createdVpc) + if err != nil { + log.Error(err, "failed to read default SNAT ip from VPC", "VPC", createdVpc.Id) + return common.ResultRequeueAfter10sec, err + } + } + + // if lb vpc enabled, read avi subnet path and cidr + // nsx bug, if set LoadBalancerVpcEndpoint.Enabled to false, when read this vpc back, + // LoadBalancerVpcEndpoint.Enabled will become a nil pointer. + if createdVpc.LoadBalancerVpcEndpoint.Enabled != nil && *createdVpc.LoadBalancerVpcEndpoint.Enabled { + path, cidr, err = r.Service.GetAVISubnetInfo(*createdVpc) + if err != nil { + log.Error(err, "failed to read lb subnet path and cidr", "VPC", createdVpc.Id) + return common.ResultRequeueAfter10sec, err + } + } + + updateSuccess(r.Service.NSXConfig, &ctx, obj, r.Client, *createdVpc.Path, snatIP, path, cidr, nc.PrivateIPv4CIDRs) + } else { + if controllerutil.ContainsFinalizer(obj, commonservice.VPCFinalizerName) { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeVPC) + vpcs := r.Service.GetVPCsByNamespace(obj.GetNamespace()) + // if nsx resource do not exist, continue to remove finalizer, or the crd can not be removed + if len(vpcs) == 0 { + // when nsx vpc not found in vpc store, skip deleting NSX VPC + log.Info("can not find VPC in store, skip deleting NSX VPC, remove finalizer from VPC CR") + } else { + vpc := vpcs[0] + if err := r.Service.DeleteVPC(*vpc.Path); err != nil { + log.Error(err, "failed to delete VPC CR, would retry exponentially", "VPC", req.NamespacedName) + deleteFail(r.Service.NSXConfig, &ctx, obj, &err, r.Client) + return common.ResultRequeueAfter10sec, err + } + + if err := r.Service.DeleteIPBlockInVPC(vpc); err != nil { + log.Error(err, "failed to delete private ip blocks for VPC", "VPC", req.NamespacedName) + } + } + + controllerutil.RemoveFinalizer(obj, commonservice.VPCFinalizerName) + if err := r.Client.Update(ctx, obj); err != nil { + deleteFail(r.Service.NSXConfig, &ctx, obj, &err, r.Client) + return common.ResultRequeue, err + } + log.V(1).Info("removed finalizer", "VPC", req.NamespacedName) + deleteSuccess(r.Service.NSXConfig, &ctx, obj) + } else { + // only print a message because it's not a normal case + log.Info("finalizers cannot be recognized", "VPC", req.NamespacedName) + } + } + return common.ResultNormal, nil +} + +func (r *VPCReconciler) setupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.VPC{}). + WithOptions( + controller.Options{ + MaxConcurrentReconciles: runtime.NumCPU(), + }). + Watches( + // For created/removed network config, add/remove from vpc network config cache. + // For modified network config, currently only support appending ips to public ip blocks, + // update network config in cache and update nsx vpc object. + &source.Kind{Type: &v1alpha1.VPCNetworkConfiguration{}}, + &VPCNetworkConfigurationHandler{ + Client: mgr.GetClient(), + vpcService: r.Service, + }, + builder.WithPredicates(VPCNetworkConfigurationPredicate)). + Complete(r) +} + +// Start setup manager and launch GC +func (r *VPCReconciler) Start(mgr ctrl.Manager) error { + err := r.setupWithManager(mgr) + if err != nil { + return err + } + + go r.GarbageCollector(make(chan bool), commonservice.GCInterval) + return nil +} + +// GarbageCollector collect vpc which has been removed from crd. +// cancel is used to break the loop during UT +func (r *VPCReconciler) GarbageCollector(cancel chan bool, timeout time.Duration) { + ctx := context.Background() + log.Info("VPC garbage collector started") + for { + select { + case <-cancel: + return + case <-time.After(timeout): + } + nsxVPCList := r.Service.ListVPC() + if len(nsxVPCList) == 0 { + continue + } + + crdVPCList := &v1alpha1.VPCList{} + err := r.Client.List(ctx, crdVPCList) + if err != nil { + log.Error(err, "failed to list VPC CR") + continue + } + + crdVPCSet := sets.NewString() + for _, vc := range crdVPCList.Items { + crdVPCSet.Insert(string(vc.UID)) + } + + for _, elem := range nsxVPCList { + if crdVPCSet.Has(*elem.Id) { + continue + } + + log.V(1).Info("GC collected nsx VPC object", "ID", elem.Id) + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteTotal, common.MetricResTypeVPC) + err = r.Service.DeleteVPC(*elem.Path) + if err != nil { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteFailTotal, common.MetricResTypeVPC) + } else { + metrics.CounterInc(r.Service.NSXConfig, metrics.ControllerDeleteSuccessTotal, common.MetricResTypeVPC) + if err := r.Service.DeleteIPBlockInVPC(elem); err != nil { + log.Error(err, "failed to delete private ip blocks for VPC", "VPC", *elem.DisplayName) + } + log.Info("deleted private ip blocks for VPC", "VPC", *elem.DisplayName) + } + } + } +} + +func StartVPCController(mgr ctrl.Manager, commonService commonservice.Service) { + vpcReconcile := VPCReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if vpcService, err := vpc.InitializeVPC(commonService); err != nil { + log.Error(err, "failed to initialize VPC commonService") + os.Exit(1) + } else { + vpcReconcile.Service = vpcService + } + if err := vpcReconcile.Start(mgr); err != nil { + log.Error(err, "failed to create VPC controller") + os.Exit(1) + } +} diff --git a/pkg/controllers/vpc/vpc_utils.go b/pkg/controllers/vpc/vpc_utils.go new file mode 100644 index 000000000..9aa1d0a2a --- /dev/null +++ b/pkg/controllers/vpc/vpc_utils.go @@ -0,0 +1,144 @@ +package vpc + +import ( + "context" + "errors" + "fmt" + "reflect" + "strings" + + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/metrics" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +func setVPCReadyStatusFalse(ctx *context.Context, vpc *v1alpha1.VPC, err *error, client client.Client) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionFalse, + Message: "NSX VPC could not be created/updated", + Reason: fmt.Sprintf("Error occurred while processing the VPC CR. Please check the config and try again. Error: %v", *err), + }, + } + updateVPCStatusConditions(ctx, vpc, newConditions, client, "", "", "", "", []string{}) +} + +func updateVPCStatusConditions(ctx *context.Context, vpc *v1alpha1.VPC, newConditions []v1alpha1.Condition, client client.Client, path string, snatIP string, + subnetPath string, cidr string, privateCidrs []string) { + conditionsUpdated := false + statusUpdated := false + for i := range newConditions { + if mergeVPCStatusCondition(ctx, vpc, &newConditions[i]) { + conditionsUpdated = true + } + } + if vpc.Status.NSXResourcePath != path || vpc.Status.DefaultSNATIP != snatIP || vpc.Status.LBSubnetPath != subnetPath || vpc.Status.LBSubnetCIDR != cidr || len(vpc.Status.PrivateIPv4CIDRs) != len(privateCidrs) { + vpc.Status.NSXResourcePath = path + vpc.Status.DefaultSNATIP = snatIP + vpc.Status.LBSubnetPath = subnetPath + vpc.Status.LBSubnetCIDR = cidr + vpc.Status.PrivateIPv4CIDRs = privateCidrs + statusUpdated = true + } + + if conditionsUpdated || statusUpdated { + + client.Status().Update(*ctx, vpc) + log.V(1).Info("updated VPC CRD", "Name", vpc.Name, "Namespace", vpc.Namespace, "Conditions", newConditions) + } +} + +func deleteFail(nsxConfig *config.NSXOperatorConfig, c *context.Context, o *v1alpha1.VPC, e *error, client client.Client) { + setVPCReadyStatusFalse(c, o, e, client) + metrics.CounterInc(nsxConfig, metrics.ControllerDeleteFailTotal, common.MetricResTypeVPC) +} + +func updateFail(nsxConfig *config.NSXOperatorConfig, c *context.Context, o *v1alpha1.VPC, e *error, client client.Client) { + setVPCReadyStatusFalse(c, o, e, client) + metrics.CounterInc(nsxConfig, metrics.ControllerUpdateFailTotal, MetricResType) +} + +func updateSuccess(nsxConfig *config.NSXOperatorConfig, c *context.Context, o *v1alpha1.VPC, client client.Client, + path string, snatIP string, subnetPath string, cidr string, privateCidrs []string) { + setVPCReadyStatusTrue(c, o, client, path, snatIP, subnetPath, cidr, privateCidrs) + metrics.CounterInc(nsxConfig, metrics.ControllerUpdateSuccessTotal, common.MetricResTypeVPC) +} + +func deleteSuccess(nsxConfig *config.NSXOperatorConfig, _ *context.Context, _ *v1alpha1.VPC) { + metrics.CounterInc(nsxConfig, metrics.ControllerDeleteSuccessTotal, common.MetricResTypeVPC) +} + +func setVPCReadyStatusTrue(ctx *context.Context, vpc *v1alpha1.VPC, client client.Client, path, snatIP, subnetPath, cidr string, privateCidrs []string) { + newConditions := []v1alpha1.Condition{ + { + Type: v1alpha1.Ready, + Status: v1.ConditionTrue, + Message: "NSX VPC has been successfully created/updated", + Reason: "NSX API returned 200 response code for PATCH", + }, + } + updateVPCStatusConditions(ctx, vpc, newConditions, client, path, snatIP, subnetPath, cidr, privateCidrs) +} + +func mergeVPCStatusCondition(ctx *context.Context, vpc *v1alpha1.VPC, newCondition *v1alpha1.Condition) bool { + matchedCondition := getExistingConditionOfType(newCondition.Type, vpc.Status.Conditions) + + if reflect.DeepEqual(matchedCondition, newCondition) { + log.V(2).Info("conditions already exist", "New Condition", newCondition, "Existing Condition", matchedCondition) + return false + } + + if matchedCondition != nil { + matchedCondition.Reason = newCondition.Reason + matchedCondition.Message = newCondition.Message + matchedCondition.Status = newCondition.Status + } else { + vpc.Status.Conditions = append(vpc.Status.Conditions, *newCondition) + } + return true +} + +func getExistingConditionOfType(conditionType v1alpha1.ConditionType, existingConditions []v1alpha1.Condition) *v1alpha1.Condition { + for i := range existingConditions { + if existingConditions[i].Type == v1alpha1.ConditionType(conditionType) { + return &existingConditions[i] + } + } + return nil +} + +// parse org id and project id from nsxtProject path +// example /orgs/default/projects/nsx_operator_e2e_test +func nsxtProjectPathToId(path string) (string, string, error) { + parts := strings.Split(path, "/") + if len(parts) < 4 { + return "", "", errors.New("Invalid NSXT project path") + } + return parts[2], parts[len(parts)-1], nil +} + +func buildNetworkConfigInfo(vpcConfigCR v1alpha1.VPCNetworkConfiguration) (*vpc.VPCNetworkConfigInfo, error) { + org, project, err := nsxtProjectPathToId(vpcConfigCR.Spec.NSXTProject) + if err != nil { + log.Error(err, "failed to parse nsx-t project in network config", "Project Path", vpcConfigCR.Spec.NSXTProject) + return nil, err + } + ninfo := &vpc.VPCNetworkConfigInfo{ + Org: org, + Name: vpcConfigCR.Name, + DefaultGatewayPath: vpcConfigCR.Spec.DefaultGatewayPath, + EdgeClusterPath: vpcConfigCR.Spec.EdgeClusterPath, + NsxtProject: project, + ExternalIPv4Blocks: vpcConfigCR.Spec.ExternalIPv4Blocks, + PrivateIPv4CIDRs: vpcConfigCR.Spec.PrivateIPv4CIDRs, + DefaultIPv4SubnetSize: vpcConfigCR.Spec.DefaultIPv4SubnetSize, + DefaultSubnetAccessMode: vpcConfigCR.Spec.DefaultSubnetAccessMode, + } + return ninfo, nil +} diff --git a/pkg/controllers/vpc/vpc_utils_test.go b/pkg/controllers/vpc/vpc_utils_test.go new file mode 100644 index 000000000..2064c4936 --- /dev/null +++ b/pkg/controllers/vpc/vpc_utils_test.go @@ -0,0 +1,107 @@ +package vpc + +import ( + "testing" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + + "github.com/stretchr/testify/assert" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +func TestNsxtProjectPathToId(t *testing.T) { + type args struct { + createEvent event.CreateEvent + l workqueue.RateLimitingInterface + } + tests := []struct { + name string + path string + org string + project string + err interface{} + }{ + {"1", "/orgs/default/projects/nsx_operator_e2e_test", "default", "nsx_operator_e2e_test", nil}, + {"2", "", "", "", "dummy"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o, p, e := nsxtProjectPathToId(tt.path) + if tt.err != nil { + assert.NotNil(t, e) + } else { + assert.Nil(t, e) + } + assert.Equal(t, tt.org, o) + assert.Equal(t, tt.project, p) + }) + } +} + +func TestBuildNetworkConfigInfo(t *testing.T) { + emptyCRD := &v1alpha1.VPCNetworkConfiguration{} + emptyCRD2 := &v1alpha1.VPCNetworkConfiguration{ + Spec: v1alpha1.VPCNetworkConfigurationSpec{ + NSXTProject: "/invalid/path", + }, + } + _, e := buildNetworkConfigInfo(*emptyCRD) + assert.NotNil(t, e) + _, e = buildNetworkConfigInfo(*emptyCRD2) + assert.NotNil(t, e) + + spec1 := v1alpha1.VPCNetworkConfigurationSpec{ + DefaultGatewayPath: "test-gw-path-1", + EdgeClusterPath: "test-edge-path-1", + ExternalIPv4Blocks: []string{"external-ipb-1", "external-ipb-2"}, + PrivateIPv4CIDRs: []string{"private-ipb-1", "private-ipb-2"}, + DefaultIPv4SubnetSize: 64, + DefaultSubnetAccessMode: "Public", + NSXTProject: "/orgs/default/projects/nsx_operator_e2e_test", + } + spec2 := v1alpha1.VPCNetworkConfigurationSpec{ + DefaultGatewayPath: "test-gw-path-2", + EdgeClusterPath: "test-edge-path-2", + ExternalIPv4Blocks: []string{"external-ipb-1", "external-ipb-2"}, + PrivateIPv4CIDRs: []string{"private-ipb-1", "private-ipb-2"}, + DefaultIPv4SubnetSize: 32, + DefaultSubnetAccessMode: "Private", + NSXTProject: "/orgs/anotherOrg/projects/anotherProject", + } + testCRD1 := v1alpha1.VPCNetworkConfiguration{ + Spec: spec1, + } + testCRD1.Name = "test-1" + testCRD2 := v1alpha1.VPCNetworkConfiguration{ + Spec: spec2, + } + testCRD2.Name = "test-2" + + tests := []struct { + name string + nc v1alpha1.VPCNetworkConfiguration + gw string + edge string + org string + project string + subnetSize int + accessMode string + }{ + {"1", testCRD1, "test-gw-path-1", "test-edge-path-1", "default", "nsx_operator_e2e_test", 64, "Public"}, + {"2", testCRD2, "test-gw-path-2", "test-edge-path-2", "anotherOrg", "anotherProject", 32, "Private"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nc, e := buildNetworkConfigInfo(tt.nc) + assert.Nil(t, e) + assert.Equal(t, tt.gw, nc.DefaultGatewayPath) + assert.Equal(t, tt.edge, nc.EdgeClusterPath) + assert.Equal(t, tt.org, nc.Org) + assert.Equal(t, tt.project, nc.NsxtProject) + assert.Equal(t, tt.subnetSize, nc.DefaultIPv4SubnetSize) + assert.Equal(t, tt.accessMode, nc.DefaultSubnetAccessMode) + }) + } + +} diff --git a/pkg/controllers/vpc/vpcnetworkconfig_handler.go b/pkg/controllers/vpc/vpcnetworkconfig_handler.go new file mode 100644 index 000000000..d6c5a62ad --- /dev/null +++ b/pkg/controllers/vpc/vpcnetworkconfig_handler.go @@ -0,0 +1,111 @@ +package vpc + +import ( + "context" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" +) + +// VPCNetworkConfigurationHandler handles VPC NetworkConfiguration event, and reconcile VPC event: +// - VPC Network Configuration creation: Add VPC Network Configuration into cache. +// - VPC Network Configuration deletion: Delete VPC Network Configuration from cache. +// - VPC Network Configuration update: Only support updating external/private ipblocks, update values in cache + +type VPCNetworkConfigurationHandler struct { + Client client.Client + vpcService *vpc.VPCService +} + +func (h *VPCNetworkConfigurationHandler) Create(e event.CreateEvent, _ workqueue.RateLimitingInterface) { + vpcConfigCR := e.Object.(*v1alpha1.VPCNetworkConfiguration) + vname := vpcConfigCR.GetName() + ninfo, _err := buildNetworkConfigInfo(*vpcConfigCR) + if _err != nil { + log.Error(_err, "processing network config add event failed") + return + } + log.Info("create network config and update to store", "NetworkConfigInfo", ninfo) + h.vpcService.RegisterVPCNetworkConfig(vname, *ninfo) +} + +func (h *VPCNetworkConfigurationHandler) Delete(e event.DeleteEvent, _ workqueue.RateLimitingInterface) { + // Currently we do not support deleting networkconfig + log.V(1).Info("do not support VPC network config deletion") +} + +func (h *VPCNetworkConfigurationHandler) Generic(_ event.GenericEvent, _ workqueue.RateLimitingInterface) { + log.V(1).Info("VPCNetworkConfiguration generic event, do nothing") +} + +func (h *VPCNetworkConfigurationHandler) Update(e event.UpdateEvent, q workqueue.RateLimitingInterface) { + log.V(1).Info("start processing VPC network config update event") + oldNc := e.ObjectOld.(*v1alpha1.VPCNetworkConfiguration) + newNc := e.ObjectNew.(*v1alpha1.VPCNetworkConfiguration) + + if getListSize(oldNc.Spec.ExternalIPv4Blocks) == getListSize(newNc.Spec.ExternalIPv4Blocks) && + getListSize(oldNc.Spec.PrivateIPv4CIDRs) == getListSize(newNc.Spec.PrivateIPv4CIDRs) { + log.V(1).Info("only support updating external/private ipv4 cidr, no change") + return + } + + // update network config info in store + info, err := buildNetworkConfigInfo(*newNc) + if err != nil { + log.Error(err, "failed to process network config update event") + return + } + h.vpcService.RegisterVPCNetworkConfig(newNc.Name, *info) + + nss := h.vpcService.GetNamespacesByNetworkconfigName(newNc.Name) + ctx := context.Background() + // find vpcs under each ns, and reconcile the vpc object + for _, ns := range nss { + vpcList := &v1alpha1.VPCList{} + err := h.Client.List(ctx, vpcList, client.InNamespace(ns)) + if err != nil { + log.Error(err, "failed to list VPCs in namespace", "Namespace", ns) + continue + } + + for _, vpc := range vpcList.Items { + log.Info("reconcile VPC CR due to modifying network config CR", "VPC", vpc.Name, "Namespace", ns, "NetworkConfig", newNc.Name) + q.Add(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vpc.Name, + Namespace: vpc.Namespace, + }, + }) + } + } +} + +var VPCNetworkConfigurationPredicate = predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + GenericFunc: func(genericEvent event.GenericEvent) bool { + return false + }, +} + +func getListSize(s []string) int { + if s == nil { + return 0 + } else { + return len(s) + } +} diff --git a/pkg/mock/staticrouteclient/client.go b/pkg/mock/staticrouteclient/client.go new file mode 100644 index 000000000..0e7259787 --- /dev/null +++ b/pkg/mock/staticrouteclient/client.go @@ -0,0 +1,108 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs (interfaces: StaticRoutesClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + model "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +// MockStaticRoutesClient is a mock of StaticRoutesClient interface. +type MockStaticRoutesClient struct { + ctrl *gomock.Controller + recorder *MockStaticRoutesClientMockRecorder +} + +// MockStaticRoutesClientMockRecorder is the mock recorder for MockStaticRoutesClient. +type MockStaticRoutesClientMockRecorder struct { + mock *MockStaticRoutesClient +} + +// NewMockStaticRoutesClient creates a new mock instance. +func NewMockStaticRoutesClient(ctrl *gomock.Controller) *MockStaticRoutesClient { + mock := &MockStaticRoutesClient{ctrl: ctrl} + mock.recorder = &MockStaticRoutesClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStaticRoutesClient) EXPECT() *MockStaticRoutesClientMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockStaticRoutesClient) Delete(arg0, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockStaticRoutesClientMockRecorder) Delete(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStaticRoutesClient)(nil).Delete), arg0, arg1, arg2, arg3) +} + +// Get mocks base method. +func (m *MockStaticRoutesClient) Get(arg0, arg1, arg2, arg3 string) (model.StaticRoutes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(model.StaticRoutes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockStaticRoutesClientMockRecorder) Get(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStaticRoutesClient)(nil).Get), arg0, arg1, arg2, arg3) +} + +// List mocks base method. +func (m *MockStaticRoutesClient) List(arg0, arg1, arg2 string, arg3 *string, arg4 *bool, arg5 *string, arg6 *int64, arg7 *bool, arg8 *string) (model.StaticRoutesListResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) + ret0, _ := ret[0].(model.StaticRoutesListResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockStaticRoutesClientMockRecorder) List(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockStaticRoutesClient)(nil).List), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +} + +// Patch mocks base method. +func (m *MockStaticRoutesClient) Patch(arg0, arg1, arg2, arg3 string, arg4 model.StaticRoutes) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Patch", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockStaticRoutesClientMockRecorder) Patch(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockStaticRoutesClient)(nil).Patch), arg0, arg1, arg2, arg3, arg4) +} + +// Update mocks base method. +func (m *MockStaticRoutesClient) Update(arg0, arg1, arg2, arg3 string, arg4 model.StaticRoutes) (model.StaticRoutes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(model.StaticRoutes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockStaticRoutesClientMockRecorder) Update(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStaticRoutesClient)(nil).Update), arg0, arg1, arg2, arg3, arg4) +} diff --git a/pkg/nsx/client.go b/pkg/nsx/client.go index a051465df..205058dc1 100644 --- a/pkg/nsx/client.go +++ b/pkg/nsx/client.go @@ -18,7 +18,14 @@ import ( "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/domains" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/domains/security_policies" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/sites/enforcement_points" - vpc_search "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/search" + projects "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + infra "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/infra" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/infra/realized_state" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs" + nat "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/nat" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets/ip_pools" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets/ports" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/search" "github.com/vmware-tanzu/nsx-operator/pkg/config" @@ -41,35 +48,58 @@ type Client struct { NsxConfig *config.NSXOperatorConfig RestConnector *client.RestConnector - QueryClient search.QueryClient - VPCQueryClient vpc_search.QueryClient - GroupClient domains.GroupsClient - SecurityClient domains.SecurityPoliciesClient - RuleClient security_policies.RulesClient - InfraClient nsx_policy.InfraClient - ClusterControlPlanesClient enforcement_points.ClusterControlPlanesClient + QueryClient search.QueryClient + GroupClient domains.GroupsClient + SecurityClient domains.SecurityPoliciesClient + RuleClient security_policies.RulesClient + InfraClient nsx_policy.InfraClient - MPQueryClient mpsearch.QueryClient - CertificatesClient trust_management.CertificatesClient - PrincipalIdentitiesClient trust_management.PrincipalIdentitiesClient - WithCertificateClient principal_identities.WithCertificateClient + ClusterControlPlanesClient enforcement_points.ClusterControlPlanesClient + HostTransPortNodesClient enforcement_points.HostTransportNodesClient + SubnetStatusClient subnets.StatusClient + RealizedEntitiesClient realized_state.RealizedEntitiesClient + MPQueryClient mpsearch.QueryClient + CertificatesClient trust_management.CertificatesClient + PrincipalIdentitiesClient trust_management.PrincipalIdentitiesClient + WithCertificateClient principal_identities.WithCertificateClient + + OrgRootClient nsx_policy.OrgRootClient + ProjectInfraClient projects.InfraClient + VPCClient projects.VpcsClient + IPBlockClient infra.IpBlocksClient + StaticRouteClient vpcs.StaticRoutesClient + NATRuleClient nat.NatRulesClient + VpcGroupClient vpcs.GroupsClient + PortClient subnets.PortsClient + PortStateClient ports.StateClient + IPPoolClient subnets.IpPoolsClient + IPAllocationClient ip_pools.IpAllocationsClient + SubnetsClient vpcs.SubnetsClient + RealizedStateClient realized_state.RealizedEntitiesClient NSXChecker NSXHealthChecker NSXVerChecker NSXVersionChecker } -var nsx320Version = [3]int64{3, 2, 0} -var nsx401Version = [3]int64{4, 0, 1} -var nsx412Version = [3]int64{4, 1, 2} -var nsx413Version = [3]int64{4, 1, 3} +var ( + nsx320Version = [3]int64{3, 2, 0} + nsx401Version = [3]int64{4, 0, 1} + nsx411Version = [3]int64{4, 1, 1} + nsx412Version = [3]int64{4, 1, 2} + nsx413Version = [3]int64{4, 1, 3} +) type NSXHealthChecker struct { cluster *Cluster } type NSXVersionChecker struct { - cluster *Cluster - featureSupported [AllFeatures]bool + cluster *Cluster + securityPolicySupported bool + nsxServiceAccountSupported bool + nsxServiceAccountRestoreSupported bool + vpcSupported bool + featureSupported [AllFeatures]bool } func (ck *NSXHealthChecker) CheckNSXHealth(req *http.Request) error { @@ -91,7 +121,12 @@ func GetClient(cf *config.NSXOperatorConfig) *Client { // Set log level for vsphere-automation-sdk-go logger := logrus.New() vspherelog.SetLogger(logger) - c := NewConfig(strings.Join(cf.NsxApiManagers, ","), cf.NsxApiUser, cf.NsxApiPassword, cf.CaFile, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, cf.GetTokenProvider(), nil, cf.Thumbprint) + defaultHttpTimeout := 20 + if cf.DefaultTimeout > 0 { + defaultHttpTimeout = cf.DefaultTimeout + } + c := NewConfig(strings.Join(cf.NsxApiManagers, ","), cf.NsxApiUser, cf.NsxApiPassword, cf.CaFile, 10, 3, defaultHttpTimeout, 20, true, true, true, + ratelimiter.AIMD, cf.GetTokenProvider(), nil, cf.Thumbprint) cluster, _ := NewCluster(c) queryClient := search.NewQueryClient(restConnector(cluster)) @@ -99,14 +134,30 @@ func GetClient(cf *config.NSXOperatorConfig) *Client { securityClient := domains.NewSecurityPoliciesClient(restConnector(cluster)) ruleClient := security_policies.NewRulesClient(restConnector(cluster)) infraClient := nsx_policy.NewInfraClient(restConnector(cluster)) - vpcQueryClient := vpc_search.NewQueryClient(restConnector(cluster)) - clusterControlPlanesClient := enforcement_points.NewClusterControlPlanesClient(restConnector(cluster)) + clusterControlPlanesClient := enforcement_points.NewClusterControlPlanesClient(restConnector(cluster)) + hostTransportNodesClient := enforcement_points.NewHostTransportNodesClient(restConnector(cluster)) + realizedEntitiesClient := realized_state.NewRealizedEntitiesClient(restConnector(cluster)) mpQueryClient := mpsearch.NewQueryClient(restConnector(cluster)) certificatesClient := trust_management.NewCertificatesClient(restConnector(cluster)) principalIdentitiesClient := trust_management.NewPrincipalIdentitiesClient(restConnector(cluster)) withCertificateClient := principal_identities.NewWithCertificateClient(restConnector(cluster)) + orgRootClient := nsx_policy.NewOrgRootClient(restConnector(cluster)) + projectInfraClient := projects.NewInfraClient(restConnector(cluster)) + vpcClient := projects.NewVpcsClient(restConnector(cluster)) + ipBlockClient := infra.NewIpBlocksClient(restConnector(cluster)) + staticRouteClient := vpcs.NewStaticRoutesClient(restConnector(cluster)) + natRulesClient := nat.NewNatRulesClient(restConnector(cluster)) + vpcGroupClient := vpcs.NewGroupsClient(restConnector(cluster)) + portClient := subnets.NewPortsClient(restConnector(cluster)) + portStateClient := ports.NewStateClient(restConnector(cluster)) + ipPoolClient := subnets.NewIpPoolsClient(restConnector(cluster)) + ipAllocationClient := ip_pools.NewIpAllocationsClient(restConnector(cluster)) + subnetsClient := vpcs.NewSubnetsClient(restConnector(cluster)) + subnetStatusClient := subnets.NewStatusClient(restConnector(cluster)) + realizedStateClient := realized_state.NewRealizedEntitiesClient(restConnector(cluster)) + nsxChecker := &NSXHealthChecker{ cluster: cluster, } @@ -125,15 +176,30 @@ func GetClient(cf *config.NSXOperatorConfig) *Client { InfraClient: infraClient, ClusterControlPlanesClient: clusterControlPlanesClient, - - MPQueryClient: mpQueryClient, - CertificatesClient: certificatesClient, - PrincipalIdentitiesClient: principalIdentitiesClient, - WithCertificateClient: withCertificateClient, - - NSXChecker: *nsxChecker, - NSXVerChecker: *nsxVersionChecker, - VPCQueryClient: vpcQueryClient, + HostTransPortNodesClient: hostTransportNodesClient, + RealizedEntitiesClient: realizedEntitiesClient, + MPQueryClient: mpQueryClient, + CertificatesClient: certificatesClient, + PrincipalIdentitiesClient: principalIdentitiesClient, + WithCertificateClient: withCertificateClient, + + OrgRootClient: orgRootClient, + ProjectInfraClient: projectInfraClient, + VPCClient: vpcClient, + IPBlockClient: ipBlockClient, + StaticRouteClient: staticRouteClient, + NATRuleClient: natRulesClient, + VpcGroupClient: vpcGroupClient, + PortClient: portClient, + PortStateClient: portStateClient, + SubnetStatusClient: subnetStatusClient, + + NSXChecker: *nsxChecker, + NSXVerChecker: *nsxVersionChecker, + IPPoolClient: ipPoolClient, + IPAllocationClient: ipAllocationClient, + SubnetsClient: subnetsClient, + RealizedStateClient: realizedStateClient, } // NSX version check will be restarted during SecurityPolicy reconcile // So, it's unnecessary to exit even if failed in the first time diff --git a/pkg/nsx/client_test.go b/pkg/nsx/client_test.go index 5ef5e749f..ccdd6d44d 100644 --- a/pkg/nsx/client_test.go +++ b/pkg/nsx/client_test.go @@ -4,11 +4,14 @@ package nsx import ( + "fmt" "net/http" "reflect" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" "github.com/agiledragon/gomonkey" @@ -136,3 +139,37 @@ func TestGetClient(t *testing.T) { func IsInstanceOf(objectPtr, typePtr interface{}) bool { return reflect.TypeOf(objectPtr) == reflect.TypeOf(typePtr) } + +func TestSRGetClient(t *testing.T) { + cf := config.NSXOperatorConfig{NsxConfig: &config.NsxConfig{NsxApiUser: "admin", NsxApiPassword: "Admin!23Admin", NsxApiManagers: []string{"10.173.82.128"}}} + cf.VCConfig = &config.VCConfig{} + client := GetClient(&cf) + st, error := client.StaticRouteClient.Get("default", "project-1", "vpc-2", "site1") + if error == nil { + fmt.Printf("sr %v\n", *st.ResourceType) + } else { + fmt.Printf("error %v\n", error) + } + st1 := st + ip := "10.0.0.2" + dis := int64(1) + nexthop := model.RouterNexthop{IpAddress: &ip, AdminDistance: &dis} + st1.NextHops = append(st1.NextHops, nexthop) + st, error = client.StaticRouteClient.Update("default", "project-1", "vpc-2", "site1", st1) + if error == nil { + fmt.Printf("sr %v\n", *st.ResourceType) + } else { + fmt.Printf("error %v\n", error) + } + + error = client.StaticRouteClient.Delete("default", "project-1", "vpc-2", "site1") + if error == nil { + fmt.Printf("delete succ") + } else { + fmt.Printf("delete error %v\n", error) + } + a := "/orgs/default/projects/project-1/vpcs/vpc-2/static-routes/site1" + b := strings.Split(a, "/") + fmt.Printf("b is %v \n", b[2]) + +} diff --git a/pkg/nsx/cluster.go b/pkg/nsx/cluster.go index 65576b427..edabe7d83 100644 --- a/pkg/nsx/cluster.go +++ b/pkg/nsx/cluster.go @@ -124,11 +124,11 @@ func (cluster *Cluster) getThumbprint(addr string) string { func (cluster *Cluster) getCaFile(addr string) string { host := addr[:strings.Index(addr, ":")] var cafile string - caCount := len(cluster.config.CAFile) - if caCount == 1 { + tpCount := len(cluster.config.CAFile) + if tpCount == 1 { cafile = cluster.config.CAFile[0] } - if caCount > 1 { + if tpCount > 1 { for index, ep := range cluster.endpoints { epHost := ep.Host() if pos := strings.Index(ep.Host(), ":"); pos > 0 { @@ -144,57 +144,62 @@ func (cluster *Cluster) getCaFile(addr string) string { } func (cluster *Cluster) createTransport(idle time.Duration) *Transport { - dial := func(network, addr string) (net.Conn, error) { - var config *tls.Config - cafile := cluster.getCaFile(addr) - caCount := len(cluster.config.CAFile) - if caCount > 0 { - caCert, err := os.ReadFile(cafile) + tr := &http.Transport{ + IdleConnTimeout: idle * time.Second, + } + if cluster.config.Insecure == false { + dial := func(network, addr string) (net.Conn, error) { + var config *tls.Config + cafile := cluster.getCaFile(addr) + caCount := len(cluster.config.CAFile) + if caCount > 0 { + caCert, err := os.ReadFile(cafile) + if err != nil { + log.Error(err, "create transport", "read ca file", cafile) + return nil, err + } + + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(caCert) + + config = &tls.Config{ + RootCAs: certPool, + } + + } else { + thumbprint := cluster.getThumbprint(addr) + tpCount := len(cluster.config.Thumbprint) + config = &tls.Config{ + InsecureSkipVerify: true, + VerifyConnection: func(cs tls.ConnectionState) error { + // not check thumbprint if no thumbprint config + if tpCount > 0 { + fingerprint := calcFingerprint(cs.PeerCertificates[0].Raw) + if strings.Compare(fingerprint, thumbprint) == 0 { + return nil + } else { + err := errors.New("server certificate didn't match trusted fingerprint") + log.Error(err, "verify thumbprint", "address", addr, "server thumbprint", fingerprint, "local thumbprint", thumbprint) + return err + } + } + return nil + }, + } + } + conn, err := tls.Dial(network, addr, config) if err != nil { - log.Error(err, "create transport", "read ca file", cafile) + log.Error(err, "transport connect to", "addr", addr) return nil, err - } - - certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM(caCert) - config = &tls.Config{ - RootCAs: certPool, - } - - } else { - thumbprint := cluster.getThumbprint(addr) - tpCount := len(cluster.config.Thumbprint) - config = &tls.Config{ - InsecureSkipVerify: true, - VerifyConnection: func(cs tls.ConnectionState) error { - // not check thumbprint if no thumbprint config - if tpCount > 0 { - fingerprint := calcFingerprint(cs.PeerCertificates[0].Raw) - if strings.Compare(fingerprint, thumbprint) == 0 { - return nil - } else { - err := errors.New("server certificate didn't match trusted fingerprint") - log.Error(err, "verify thumbprint", "address", addr, "server thumbprint", fingerprint, "local thumbprint", thumbprint) - return err - } - } - return nil - }, } + return conn, nil } - conn, err := tls.Dial(network, addr, config) - if err != nil { - log.Error(err, "transport connect to", "addr", addr) - return nil, err - + tr.DialTLS = dial + } else { + tr.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, } - return conn, nil - } - - tr := &http.Transport{ - DialTLS: dial, - IdleConnTimeout: idle * time.Second, } return &Transport{Base: tr} } @@ -308,12 +313,18 @@ func (nsxVersion *NsxVersion) featureSupported(feature int) bool { var minVersion [3]int64 validFeature := false switch feature { + case VPC: + minVersion = nsx411Version + validFeature = true case SecurityPolicy: minVersion = nsx320Version validFeature = true case ServiceAccount: minVersion = nsx401Version validFeature = true + case StaticRoute: + minVersion = nsx401Version + validFeature = true case ServiceAccountRestore: minVersion = nsx412Version validFeature = true diff --git a/pkg/nsx/cookie_test.go b/pkg/nsx/cookie_test.go index 568b2902f..97f37192e 100644 --- a/pkg/nsx/cookie_test.go +++ b/pkg/nsx/cookie_test.go @@ -33,6 +33,6 @@ func TestNewJar(t *testing.T) { func TestJar_Cookies(t *testing.T) { url2 := &url.URL{Host: "test"} j := NewJar() - j.SetCookies(url2, []*http.Cookie{&http.Cookie{}}) + j.SetCookies(url2, []*http.Cookie{}) assert.NotNil(t, j.Cookies(url2)) } diff --git a/pkg/nsx/endpoint.go b/pkg/nsx/endpoint.go index 7b8ae787c..be8c07041 100644 --- a/pkg/nsx/endpoint.go +++ b/pkg/nsx/endpoint.go @@ -6,7 +6,7 @@ package nsx import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strings" @@ -275,13 +275,13 @@ func (ep *Endpoint) createAuthSession(certProvider auth.ClientCertProvider, toke log.Error(err, "session creation failed", "endpoint", u.Host) return err } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { err = fmt.Errorf("session creation failed, unexpected status code %d", resp.StatusCode) } if err != nil { - log.Error(err, "session creation failed", "endpoint", u.Host, "statusCode", resp.StatusCode, "headerDate", resp.Header["Date"], "body", body) + log.Error(err, "session creation failed", "endpoint", u.Host, "statusCode", resp.StatusCode, "headerDate", resp.Header["Date"], "body", string(body)) return err } tokens, ok := resp.Header["X-Xsrf-Token"] diff --git a/pkg/nsx/endpoint_test.go b/pkg/nsx/endpoint_test.go index 2a3938cb6..7146b3042 100644 --- a/pkg/nsx/endpoint_test.go +++ b/pkg/nsx/endpoint_test.go @@ -114,7 +114,7 @@ var ( func TestCreateAuthSession(t *testing.T) { assert := assert.New(t) jar := NewJar() - cluster := &Cluster{} + cluster := &Cluster{config: &Config{}} tr := cluster.createTransport(10) client := cluster.createHTTPClient(tr, 30) noBClient := cluster.createNoBalancerClient(90, 90) @@ -171,7 +171,7 @@ func TestKeepAlive(t *testing.T) { } })) defer ts.Close() - cluster := &Cluster{} + cluster := &Cluster{config: &Config{}} tr := cluster.createTransport(10) client := cluster.createHTTPClient(tr, 30) noBClient := cluster.createNoBalancerClient(90, 90) diff --git a/pkg/nsx/services/common/builder.go b/pkg/nsx/services/common/builder.go index 6b0a1e49d..f671f2446 100644 --- a/pkg/nsx/services/common/builder.go +++ b/pkg/nsx/services/common/builder.go @@ -5,6 +5,7 @@ package common import ( "fmt" + "regexp" "strings" mpmodel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" @@ -35,3 +36,21 @@ func ConvertMPTagsToTags(mpTags []mpmodel.Tag) []model.Tag { } return tags } + +func ParseVPCResourcePath(nsxResourcePath string) (VPCResourceInfo, error) { + info := VPCResourceInfo{} + reExp := regexp.MustCompile(`/orgs/([^/]+)/projects/([^/]+)/vpcs/([^/]+)([/\S+]*)`) + matches := reExp.FindStringSubmatch(nsxResourcePath) + if len(matches) != 5 { + err := fmt.Errorf("invalid path '%s'", nsxResourcePath) + return info, err + } + info.OrgID = matches[1] + info.ProjectID = matches[2] + info.VPCID = matches[3] + layers := strings.Split(nsxResourcePath, "/") + size := len(layers) + info.ID = layers[size-1] + info.ParentID = layers[size-3] + return info, nil +} diff --git a/pkg/nsx/services/common/builder_test.go b/pkg/nsx/services/common/builder_test.go index 1d02cf36f..e18d821a9 100644 --- a/pkg/nsx/services/common/builder_test.go +++ b/pkg/nsx/services/common/builder_test.go @@ -118,3 +118,64 @@ func TestQueryTagCondition(t *testing.T) { }) } } + +func TestParseVPCResourcePath(t *testing.T) { + type args struct { + nsxResourcePath string + } + tests := []struct { + name string + args args + want VPCResourceInfo + wantErr bool + }{ + { + name: "SubnetPort Path", + args: args{ + nsxResourcePath: "/orgs/org1/projects/proj1/vpcs/vpc1/subnets/subnet1/ports/port1", + }, + want: VPCResourceInfo{ + OrgID: "org1", + ProjectID: "proj1", + VPCID: "vpc1", + ParentID: "subnet1", + ID: "port1", + }, + wantErr: false, + }, + { + name: "VPC Path", + args: args{ + nsxResourcePath: "/orgs/org1/projects/proj1/vpcs/vpc1", + }, + want: VPCResourceInfo{ + OrgID: "org1", + ProjectID: "proj1", + VPCID: "vpc1", + ParentID: "proj1", + ID: "vpc1", + }, + wantErr: false, + }, + { + name: "Invalid Path", + args: args{ + nsxResourcePath: "/abc/def", + }, + want: VPCResourceInfo{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseVPCResourcePath(tt.args.nsxResourcePath) + if (err != nil) != tt.wantErr { + t.Errorf("ParseVPCResourcePath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseVPCResourcePath() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/nsx/services/common/compare.go b/pkg/nsx/services/common/compare.go index 777455fc0..36420afaf 100644 --- a/pkg/nsx/services/common/compare.go +++ b/pkg/nsx/services/common/compare.go @@ -29,25 +29,28 @@ func CompareResources(existing []Comparable, expected []Comparable) (changed []C changed = make([]Comparable, 0) expectedMap := make(map[string]Comparable) - for _, e := range expected { - expectedMap[e.Key()] = e + for _, expected_item := range expected { + expectedMap[expected_item.Key()] = expected_item } existingMap := make(map[string]Comparable) - for _, e := range existing { - existingMap[e.Key()] = e + for _, existed_item := range existing { + existingMap[existed_item.Key()] = existed_item } - for key, e := range expectedMap { - if e2, ok := existingMap[key]; ok { - if isChanged := CompareResource(e2, e); !isChanged { + for key, expected_item := range expectedMap { + if existed_item, ok := existingMap[key]; ok { + if isChanged := CompareResource(existed_item, expected_item); !isChanged { continue + } else { + log.V(1).Info("resource changed", "existing", existed_item, "expected", expected_item) } } - changed = append(changed, e) + changed = append(changed, expected_item) } - for key, e := range existingMap { + for key, existed_item := range existingMap { if _, ok := expectedMap[key]; !ok { - stale = append(stale, e) + log.V(1).Info("resource stale", "existing", existed_item) + stale = append(stale, existed_item) } } log.V(1).Info("resources differ", "stale", stale, "changed", changed) diff --git a/pkg/nsx/services/common/store.go b/pkg/nsx/services/common/store.go index 85097bb66..29a15385f 100644 --- a/pkg/nsx/services/common/store.go +++ b/pkg/nsx/services/common/store.go @@ -2,17 +2,17 @@ package common import ( "fmt" + "net/url" "strconv" "strings" "sync" vapierrors "github.com/vmware/vsphere-automation-sdk-go/lib/vapi/std/errors" "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/tools/cache" - "github.com/vmware/vsphere-automation-sdk-go/runtime/data" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" nsxutil "github.com/vmware-tanzu/nsx-operator/pkg/nsx/util" ) @@ -28,9 +28,9 @@ type Store interface { TransResourceToStore(obj *data.StructValue) error // ListIndexFuncValues is the method to list all the values of the index ListIndexFuncValues(key string) sets.String - // Operate is the method to create, update and delete the resource to the store based + // Apply is the method to create, update and delete the resource to the store based // on its tag MarkedForDelete. - Operate(obj interface{}) error + Apply(obj interface{}) error // IsPolicyAPI returns if it is Policy resource IsPolicyAPI() bool } @@ -122,21 +122,52 @@ func TransError(err error) error { // InitializeResourceStore is the method to query all the various resources from nsx-t side and // save them to the store, we could use it to cache all the resources when process starts. -func (service *Service) InitializeResourceStore(wg *sync.WaitGroup, fatalErrors chan error, resourceTypeValue string, store Store) { +func (service *Service) InitializeResourceStore(wg *sync.WaitGroup, fatalErrors chan error, resourceTypeValue string, tags []model.Tag, store Store) { + service.InitializeCommonStore(wg, fatalErrors, "", "", resourceTypeValue, tags, store) +} + +// InitializeVPCResourceStore is the method to query all the various VPC resources from nsx-t side and +// save them to the store, we could use it to cache all the resources when process starts. +func (service *Service) InitializeVPCResourceStore(wg *sync.WaitGroup, fatalErrors chan error, org string, project string, resourceTypeValue string, tags []model.Tag, store Store) { + service.InitializeCommonStore(wg, fatalErrors, org, project, resourceTypeValue, tags, store) +} + +// InitializeCommonStore is the common method used by InitializeResourceStore and InitializeVPCResourceStore +func (service *Service) InitializeCommonStore(wg *sync.WaitGroup, fatalErrors chan error, org string, project string, resourceTypeValue string, tags []model.Tag, store Store) { defer wg.Done() tagScopeClusterKey := strings.Replace(TagScopeCluster, "/", "\\/", -1) tagScopeClusterValue := strings.Replace(service.NSXClient.NsxConfig.Cluster, ":", "\\:", -1) tagParam := fmt.Sprintf("tags.scope:%s AND tags.tag:%s", tagScopeClusterKey, tagScopeClusterValue) + + for _, tag := range tags { + tagKey := strings.Replace(*tag.Scope, "/", "\\/", -1) + tagParam += fmt.Sprintf(" AND tags.scope:%s ", tagKey) + if tag.Tag != nil { + tagValue := strings.Replace(*tag.Tag, ":", "\\:", -1) + tagParam += fmt.Sprintf(" AND tags.tag:%s ", tagValue) + } + } + resourceParam := fmt.Sprintf("%s:%s", ResourceType, resourceTypeValue) queryParam := resourceParam + " AND " + tagParam + if org != "" || project != "" { + // QueryClient.List() will escape the path, "path:" then will be "path%25%3A" instead of "path:3A", + //"path%25%3A" would fail to get response. Hack it here. + path := "\\/orgs\\/" + org + "\\/projects\\/" + project + "\\/*" + pathUnescape, _ := url.PathUnescape("path%3A") + queryParam += " AND " + pathUnescape + path + } + queryParam += " AND marked_for_delete:false" + var cursor *string = nil count := uint64(0) for { + var err error + var results []*data.StructValue var resultCount *int64 - var err error if store.IsPolicyAPI() { response, searchEerr := service.NSXClient.QueryClient.List(queryParam, cursor, nil, Int64(PageSize), nil, nil) results = response.Results diff --git a/pkg/nsx/services/common/store_test.go b/pkg/nsx/services/common/store_test.go index f711572db..a0d2143b1 100644 --- a/pkg/nsx/services/common/store_test.go +++ b/pkg/nsx/services/common/store_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/agiledragon/gomonkey/v2" + "github.com/stretchr/testify/assert" "github.com/vmware/vsphere-automation-sdk-go/lib/vapi/std/errors" "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" "github.com/vmware/vsphere-automation-sdk-go/runtime/data" @@ -83,7 +84,7 @@ func (_ *fakeQueryClient) List(_ string, _ *string, _ *string, _ *int64, _ *bool }, nil } -func (resourceStore *ResourceStore) Operate(i interface{}) error { +func (resourceStore *ResourceStore) Apply(i interface{}) error { sp := i.(*model.SecurityPolicy) for _, rule := range sp.Rules { if rule.MarkedForDelete != nil && *rule.MarkedForDelete { @@ -177,5 +178,11 @@ func Test_InitializeResourceStore(t *testing.T) { }) defer patches2.Reset() - service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, ruleStore) + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, nil, ruleStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, []string{"11111"}, ruleStore.ListKeys()) + mTag, mScope := TagScopeNamespace, "11111" + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, []model.Tag{{Tag: &mTag, Scope: &mScope}}, ruleStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, []string{"11111"}, ruleStore.ListKeys()) } diff --git a/pkg/nsx/services/common/types.go b/pkg/nsx/services/common/types.go index b16c91f3e..cdb5114c1 100644 --- a/pkg/nsx/services/common/types.go +++ b/pkg/nsx/services/common/types.go @@ -18,40 +18,112 @@ const ( HashLength int = 8 MaxTagLength int = 256 MaxIdLength int = 255 + MaxNameLength int = 255 + MaxSubnetNameLength int = 80 + TagScopeNCPCluster string = "ncp/cluster" + TagScopeNCPProjectUID string = "ncp/project_uid" + TagScopeNCPVIFProjectUID string = "ncp/vif_project_uid" + TagScopeNCPPod string = "ncp/pod" + TagScopeNCPVNETInterface string = "ncp/vnet_interface" + TagScopeVersion string = "nsx-op/version" TagScopeCluster string = "nsx-op/cluster" TagScopeNamespace string = "nsx-op/namespace" + TagScopeNamespaceUID string = "nsx-op/namespace_uid" TagScopeSecurityPolicyCRName string = "nsx-op/security_policy_cr_name" TagScopeSecurityPolicyCRUID string = "nsx-op/security_policy_cr_uid" + TagScopeStaticRouteCRName string = "nsx-op/static_route_cr_name" + TagScopeStaticRouteCRUID string = "nsx-op/static_route_cr_uid" TagScopeRuleID string = "nsx-op/rule_id" + TagScopeGoupID string = "nsx-op/group_id" TagScopeGroupType string = "nsx-op/group_type" TagScopeSelectorHash string = "nsx-op/selector_hash" TagScopeNSXServiceAccountCRName string = "nsx-op/nsx_service_account_name" TagScopeNSXServiceAccountCRUID string = "nsx-op/nsx_service_account_uid" - TagScopeNCPCluster string = "ncp/cluster" - TagScopeNCPProject string = "ncp/project" - TagScopeNCPVIFProject string = "ncp/vif_project" - TagScopeNCPPod string = "ncp/pod" - TagScopeNCPVNETInterface string = "ncp/vnet_interface" + TagScopeNSXProjectID string = "nsx-op/nsx_project_id" + TagScopeProjectGroupShared string = "nsx-op/is_nsx_project_shared" TagScopeVPCCRName string = "nsx-op/vpc_cr_name" TagScopeVPCCRUID string = "nsx-op/vpc_cr_uid" + TagScopeSubnetPortCRName string = "nsx-op/subnetport_cr_name" + TagScopeSubnetPortCRUID string = "nsx-op/subnetport_cr_uid" + TagScopeIPPoolCRName string = "nsx-op/ippool_cr_name" + TagScopeIPPoolCRUID string = "nsx-op/ippool_cr_uid" + TagScopeIPPoolCRType string = "nsx-op/ippool_cr_type" + TagScopeIPSubnetName string = "nsx-op/ipsubnet_cr_name" + TagScopeVMNamespaceUID string = "nsx-op/vm_namespace_uid" + TagScopeVMNamespace string = "nsx-op/vm_namespace" + LabelDefaultSubnetSet string = "nsxoperator.vmware.com/default-subnetset-for" + LabelDefaultVMSubnetSet string = "VirtualMachine" + LabelDefaultPodSubnetSet string = "Pod" + TagScopeSubnetCRType string = "nsx-op/subnet_cr_type" + TagScopeSubnetCRUID string = "nsx-op/subnet_cr_uid" + TagScopeSubnetCRName string = "nsx-op/subnet_cr_name" + TagScopeSubnetSetCRName string = "nsx-op/subnetset_cr_name" + TagScopeSubnetSetCRUID string = "nsx-op/subnetset_cr_uid" + TagValueGroupScope string = "scope" + TagValueGroupSrc string = "source" + TagValueGroupDst string = "destination" + AnnotationVPCNetworkConfig string = "nsx.vmware.com/vpc_network_config" + AnnotationVPCName string = "nsx.vmware.com/vpc_name" + AnnotationPodMAC string = "nsx.vmware.com/mac" + AnnotationPodAttachment string = "nsx.vmware.com/attachment" + DefaultNetworkConfigName string = "default" + TagScopePodName string = "nsx-op/pod_name" + TagScopePodUID string = "nsx-op/pod_uid" + ValueMajorVersion string = "1" + ValueMinorVersion string = "0" + ValuePatchVersion string = "0" - GCInterval = 60 * time.Second - FinalizerName = "securitypolicy.nsx.vmware.com/finalizer" + GCInterval = 60 * time.Second + RealizeTimeout = 2 * time.Minute + RealizeMaxRetries = 3 + IPPoolFinalizerName = "ippool.nsx.vmware.com/finalizer" + DefaultSNATID = "DEFAULT" + AVISubnetLBID = "_AVI_SUBNET--LB" + IPPoolTypePublic = "Public" + IPPoolTypePrivate = "Private" - NSXServiceAccountFinalizerName = "nsxserviceaccount.nsx.vmware.com/finalizer" - GCValidationInterval uint16 = 720 + SecurityPolicyFinalizerName = "securitypolicy.nsx.vmware.com/finalizer" + StaticRouteFinalizerName = "staticroute.nsx.vmware.com/finalizer" + NSXServiceAccountFinalizerName = "nsxserviceaccount.nsx.vmware.com/finalizer" + SubnetFinalizerName = "subnet.nsx.vmware.com/finalizer" + SubnetSetFinalizerName = "subnetset.nsx.vmware.com/finalizer" + SubnetPortFinalizerName = "subnetport.nsx.vmware.com/finalizer" + VPCFinalizerName = "vpc.nsx.vmware.com/finalizer" + PodFinalizerName = "pod.nsx.vmware.com/finalizer" + + IndexKeySubnetID = "IndexKeySubnetID" + IndexKeyPathPath = "Path" + IndexKeyNodeName = "IndexKeyNodeName" + GCValidationInterval uint16 = 720 ) +var TagValueVersion = []string{ValueMajorVersion, ValueMinorVersion, ValuePatchVersion} + var ( - ResourceType = "resource_type" - ResourceTypeSecurityPolicy = "SecurityPolicy" - ResourceTypeGroup = "Group" - ResourceTypeRule = "Rule" - ResourceTypeVPC = "VPC" + ResourceType = "resource_type" + ResourceTypeSecurityPolicy = "SecurityPolicy" + ResourceTypeGroup = "Group" + ResourceTypeRule = "Rule" + ResourceTypeIPBlock = "IpAddressBlock" + ResourceTypeOrgRoot = "OrgRoot" + ResourceTypeOrg = "Org" + ResourceTypeProject = "Project" + ResourceTypeVpc = "Vpc" + ResourceTypeSubnetPort = "VpcSubnetPort" + ResourceTypeVirtualMachine = "VirtualMachine" + ResourceTypeShare = "Share" + ResourceTypeSharedResource = "SharedResource" + ResourceTypeChildSharedResource = "ChildSharedResource" + ResourceTypeChildShare = "ChildShare" + // ResourceTypeClusterControlPlane is used by NSXServiceAccountController ResourceTypeClusterControlPlane = "clustercontrolplane" // ResourceTypePrincipalIdentity is used by NSXServiceAccountController, and it is MP resource type. ResourceTypePrincipalIdentity = "principalidentity" + ResourceTypeSubnet = "VpcSubnet" + ResourceTypeIPPool = "IpAddressPool" + ResourceTypeIPPoolBlockSubnet = "IpAddressPoolBlockSubnet" + ResourceTypeNode = "HostTransportNode" ) type Service struct { @@ -69,4 +141,17 @@ func NewConverter() *bindings.TypeConverter { var ( String = pointy.String // address of string Int64 = pointy.Int64 // address of int64 + Bool = pointy.Bool // address of bool ) + +type VPCResourceInfo struct { + OrgID string + ProjectID string + VPCID string + // 1. For the subnetport with path /orgs/o1/projects/p1/vpcs/v1/subnets/s1/ports/port1, + // ID=port1, ParentID=s1; + // 2. For the subnet with path /orgs/o1/projects/p1/vpcs/v1/subnets/s1, + // ID=s1, ParentID=v1 (ParentID==VPCID). + ID string + ParentID string +} diff --git a/pkg/nsx/services/ippool/builder.go b/pkg/nsx/services/ippool/builder.go new file mode 100644 index 000000000..89873c365 --- /dev/null +++ b/pkg/nsx/services/ippool/builder.go @@ -0,0 +1,120 @@ +package ippool + +import ( + "fmt" + "strings" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + Int64 = common.Int64 + String = common.String +) + +const ( + IPPOOLPREFIX = "ipc" + IPPOOLSUBNETPREFIX = "ibs" +) + +func (service *IPPoolService) BuildIPPool(IPPool *v1alpha2.IPPool) (*model.IpAddressPool, []*model.IpAddressPoolBlockSubnet) { + return &model.IpAddressPool{ + Id: String(service.buildIPPoolID(IPPool)), + DisplayName: String(service.buildIPPoolName(IPPool)), + Tags: service.buildIPPoolTags(IPPool), + }, service.buildIPSubnets(IPPool) +} + +func (service *IPPoolService) buildIPPoolID(IPPool *v1alpha2.IPPool) string { + return util.GenerateID(string(IPPool.UID), IPPOOLPREFIX, "", "") +} + +func (service *IPPoolService) buildIPPoolName(IPPool *v1alpha2.IPPool) string { + return util.GenerateDisplayName(IPPool.ObjectMeta.Name, IPPOOLPREFIX, "", "", getCluster(service)) +} + +func (service *IPPoolService) buildIPPoolTags(IPPool *v1alpha2.IPPool) []model.Tag { + basicTags := util.BuildBasicTags(getCluster(service), IPPool, "") + tags := util.AppendTags(basicTags, []model.Tag{ + {Scope: String(common.TagScopeIPPoolCRType), Tag: String(IPPool.Spec.Type)}}, + ) + return tags +} + +func (service *IPPoolService) buildIPSubnets(IPPool *v1alpha2.IPPool) []*model.IpAddressPoolBlockSubnet { + var IPSubnets []*model.IpAddressPoolBlockSubnet + for _, subnetRequest := range IPPool.Spec.Subnets { + IPSubnet := service.buildIPSubnet(IPPool, subnetRequest) + if IPSubnet != nil { + IPSubnets = append(IPSubnets, IPSubnet) + } + } + return IPSubnets +} + +func (service *IPPoolService) buildIPSubnetID(IPPool *v1alpha2.IPPool, subnetRequest *v1alpha2.SubnetRequest) string { + return util.GenerateID(string(IPPool.UID), IPPOOLSUBNETPREFIX, subnetRequest.Name, "") +} + +func (service *IPPoolService) buildIPSubnetName(IPPool *v1alpha2.IPPool, subnetRequest *v1alpha2.SubnetRequest) string { + return util.GenerateDisplayName(IPPool.ObjectMeta.Name, IPPOOLSUBNETPREFIX, subnetRequest.Name, "", getCluster(service)) +} + +func (service *IPPoolService) buildIPSubnetTags(IPPool *v1alpha2.IPPool, subnetRequest *v1alpha2.SubnetRequest) []model.Tag { + basicTags := util.BuildBasicTags(getCluster(service), IPPool, "") + tags := util.AppendTags(basicTags, []model.Tag{ + {Scope: String(common.TagScopeIPSubnetName), Tag: String(subnetRequest.Name)}}, + ) + return tags +} + +func (service *IPPoolService) buildIPSubnetIntentPath(IPPool *v1alpha2.IPPool, subnetRequest *v1alpha2.SubnetRequest) string { + if IPPool.Spec.Type == common.IPPoolTypePrivate { + VPCInfo := commonctl.ServiceMediator.ListVPCInfo(IPPool.Namespace) + if len(VPCInfo) == 0 { + return "" + } + return strings.Join([]string{fmt.Sprintf("/orgs/%s/projects/%s/infra/ip-pools", VPCInfo[0].OrgID, VPCInfo[0].ProjectID), + service.buildIPPoolID(IPPool), + "ip-subnets", service.buildIPSubnetID(IPPool, subnetRequest)}, "/") + } else { + return strings.Join([]string{"/infra/ip-pools", service.buildIPPoolID(IPPool), + "ip-subnets", service.buildIPSubnetID(IPPool, subnetRequest)}, "/") + } +} + +func (service *IPPoolService) buildIPSubnet(IPPool *v1alpha2.IPPool, subnetRequest v1alpha2.SubnetRequest) *model.IpAddressPoolBlockSubnet { + IpBlockPath := String("") + IpBlockPathList := make([]string, 0) + VPCInfo := commonctl.ServiceMediator.GetVPCsByNamespace(IPPool.Namespace) + if len(VPCInfo) == 0 { + log.Error(nil, "failed to find VPCInfo for IPPool CR", "IPPool", IPPool.Name, "namespace", IPPool.Namespace) + return nil + } + + if IPPool.Spec.Type == common.IPPoolTypePrivate { + IpBlockPathList = VPCInfo[0].PrivateIpv4Blocks + } else { + IpBlockPathList = VPCInfo[0].ExternalIpv4Blocks + } + for _, ipBlockPath := range IpBlockPathList { + if util.Contains(service.ExhaustedIPBlock, ipBlockPath) { + continue + } + IpBlockPath = String(ipBlockPath) + log.V(2).Info("use ip block path", "ip block path", ipBlockPath) + } + + return &model.IpAddressPoolBlockSubnet{ + Id: String(service.buildIPSubnetID(IPPool, &subnetRequest)), + DisplayName: String(service.buildIPSubnetName(IPPool, &subnetRequest)), + Tags: service.buildIPSubnetTags(IPPool, &subnetRequest), + Size: Int64(util.CalculateSubnetSize(subnetRequest.PrefixLength)), + IpBlockPath: IpBlockPath, + } +} diff --git a/pkg/nsx/services/ippool/builder_test.go b/pkg/nsx/services/ippool/builder_test.go new file mode 100644 index 000000000..01e63c687 --- /dev/null +++ b/pkg/nsx/services/ippool/builder_test.go @@ -0,0 +1,108 @@ +package ippool + +import ( + "reflect" + "strings" + "testing" + + "github.com/agiledragon/gomonkey" + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +func TestIPPoolService_BuildIPPool(t *testing.T) { + ipPoolService := fakeService() + + ipPool := &v1alpha2.IPPool{ + ObjectMeta: v1.ObjectMeta{ + Name: "ippool1", + UID: "uuid1", + }, + Spec: v1alpha2.IPPoolSpec{ + Type: "public", + Subnets: []v1alpha2.SubnetRequest{ + { + Name: "subnet1", + PrefixLength: 24, + }, + }, + }, + } + + want := &model.IpAddressPool{ + DisplayName: String("ipc-k8scl-one:test-ippool1"), + Id: String("ipc_uuid1"), + Tags: []model.Tag{ + {Scope: String("nsx-op/cluster"), Tag: String("k8scl-one:test")}, + {Scope: String("nsx-op/version"), Tag: String(strings.Join(common.TagValueVersion, "."))}, + {Scope: String("nsx-op/namespace"), Tag: String("")}, + {Scope: String("nsx-op/ippool_cr_name"), Tag: String("ippool1")}, + { + Scope: String("nsx-op/ippool_cr_uid"), + Tag: String("uuid1"), + }, + {Scope: String("nsx-op/ippool_cr_type"), Tag: String("public")}, + }, + } + + want2 := model.IpAddressPoolBlockSubnet{ + DisplayName: String("ibs-k8scl-one:test-ippool1-subnet1"), + Id: String("ibs_uuid1_subnet1"), + IpBlockPath: String("/infra/ip-blocks/block-test"), + Tags: []model.Tag{ + {Scope: String("nsx-op/cluster"), Tag: String("k8scl-one:test")}, + {Scope: String("nsx-op/version"), Tag: String(strings.Join(common.TagValueVersion, "."))}, + {Scope: String("nsx-op/namespace"), Tag: String("")}, + {Scope: String("nsx-op/ippool_cr_name"), Tag: String("ippool1")}, + {Scope: String("nsx-op/ippool_cr_uid"), Tag: String("uuid1")}, + {Scope: String("nsx-op/ipsubnet_cr_name"), Tag: String("subnet1")}, + }, + Size: Int64(256), + } + + vpcinfolist := []model.Vpc{ + {ExternalIpv4Blocks: []string{"/infra/ip-blocks/block-test"}}, + } + vpcCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeVPCCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: vpcCacheIndexer, + BindingType: model.VpcBindingType(), + } + vpcStore := &vpc.VPCStore{ResourceStore: resourceStore} + commonctl.ServiceMediator.VPCService = &vpc.VPCService{VpcStore: vpcStore} + patch := gomonkey.ApplyMethod(reflect.TypeOf(vpcStore), "GetVPCsByNamespace", func(vpcStore *vpc.VPCStore, + ns string, + ) []model.Vpc { + return vpcinfolist + }) + defer patch.Reset() + + type fields struct { + Service common.Service + } + type args struct { + IPPool *v1alpha2.IPPool + } + tests := []struct { + name string + args args + want *model.IpAddressPool + want1 []*model.IpAddressPoolBlockSubnet + }{ + {"test1", args{ipPool}, want, []*model.IpAddressPoolBlockSubnet{&want2}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := ipPoolService.BuildIPPool(tt.args.IPPool) + assert.Equalf(t, tt.want, got, "BuildIPPool(%v)", tt.args.IPPool) + assert.Equalf(t, tt.want1, got1, "BuildIPPool(%v)", tt.args.IPPool) + }) + } +} diff --git a/pkg/nsx/services/ippool/compare.go b/pkg/nsx/services/ippool/compare.go new file mode 100644 index 000000000..2b9a65bf2 --- /dev/null +++ b/pkg/nsx/services/ippool/compare.go @@ -0,0 +1,63 @@ +package ippool + +import ( + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type ( + IpAddressPool model.IpAddressPool + IpAddressPoolBlockSubnet model.IpAddressPoolBlockSubnet +) + +type Comparable = common.Comparable + +func (iap *IpAddressPool) Key() string { + return *iap.Id +} + +func (iapbs *IpAddressPoolBlockSubnet) Key() string { + return *iapbs.Id +} + +func (iap *IpAddressPool) Value() data.DataValue { + s := &IpAddressPool{Id: iap.Id, DisplayName: iap.DisplayName, Tags: iap.Tags} + dataValue, _ := ComparableToIpAddressPool(s).GetDataValue__() + return dataValue +} + +func (iapbs *IpAddressPoolBlockSubnet) Value() data.DataValue { + r := &IpAddressPoolBlockSubnet{Id: iapbs.Id, DisplayName: iapbs.Id, Tags: iapbs.Tags} + dataValue, _ := ComparableToIpAddressPoolBlockSubnet(r).GetDataValue__() + return dataValue +} + +func IpAddressPoolToComparable(iap *model.IpAddressPool) Comparable { + return (*IpAddressPool)(iap) +} + +func IpAddressPoolBlockSubnetsToComparable(iapbs []*model.IpAddressPoolBlockSubnet) []Comparable { + res := make([]Comparable, 0, len(iapbs)) + for i := range iapbs { + res = append(res, (*IpAddressPoolBlockSubnet)(iapbs[i])) + } + return res +} + +func ComparableToIpAddressPool(iap Comparable) *model.IpAddressPool { + return (*model.IpAddressPool)(iap.(*IpAddressPool)) +} + +func ComparableToIpAddressPoolBlockSubnets(iapbs []Comparable) []*model.IpAddressPoolBlockSubnet { + res := make([]*model.IpAddressPoolBlockSubnet, 0, len(iapbs)) + for _, iapb := range iapbs { + res = append(res, (*model.IpAddressPoolBlockSubnet)(iapb.(*IpAddressPoolBlockSubnet))) + } + return res +} + +func ComparableToIpAddressPoolBlockSubnet(iapbs Comparable) *model.IpAddressPoolBlockSubnet { + return (*model.IpAddressPoolBlockSubnet)(iapbs.(*IpAddressPoolBlockSubnet)) +} diff --git a/pkg/nsx/services/ippool/compare_test.go b/pkg/nsx/services/ippool/compare_test.go new file mode 100644 index 000000000..022ea115c --- /dev/null +++ b/pkg/nsx/services/ippool/compare_test.go @@ -0,0 +1,183 @@ +package ippool + +import ( + "reflect" + "testing" + + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +func TestComparableToIpAddressPool(t *testing.T) { + type args struct { + iap Comparable + } + tests := []struct { + name string + args args + want *model.IpAddressPool + }{ + {"1", args{&IpAddressPool{Id: String("1")}}, &model.IpAddressPool{Id: String("1")}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ComparableToIpAddressPool(tt.args.iap); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ComparableToIpAddressPool() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestComparableToIpAddressPoolBlockSubnet(t *testing.T) { + type args struct { + iapbs Comparable + } + tests := []struct { + name string + args args + want *model.IpAddressPoolBlockSubnet + }{ + {"1", args{&IpAddressPoolBlockSubnet{Id: String("1")}}, &model.IpAddressPoolBlockSubnet{Id: String("1")}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ComparableToIpAddressPoolBlockSubnet(tt.args.iapbs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ComparableToIpAddressPoolBlockSubnet() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestComparableToIpAddressPoolBlockSubnets(t *testing.T) { + type args struct { + iapbs []Comparable + } + tests := []struct { + name string + args args + want []*model.IpAddressPoolBlockSubnet + }{ + {"1", args{[]Comparable{&IpAddressPoolBlockSubnet{Id: String("1")}}}, []*model.IpAddressPoolBlockSubnet{{Id: String("1")}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ComparableToIpAddressPoolBlockSubnets(tt.args.iapbs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ComparableToIpAddressPoolBlockSubnets() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIpAddressPoolBlockSubnet_Key(t *testing.T) { + tests := []struct { + name string + iapbs IpAddressPoolBlockSubnet + want string + }{ + {"1", IpAddressPoolBlockSubnet{Id: String("1")}, "1"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.iapbs.Key(); got != tt.want { + t.Errorf("Key() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIpAddressPoolBlockSubnet_Value(t *testing.T) { + m := model.IpAddressPoolBlockSubnet{Id: String("1"), DisplayName: String("1"), Tags: []model.Tag{{Scope: String("1"), Tag: String("1")}}} + v, _ := m.GetDataValue__() + p := IpAddressPoolBlockSubnet{Id: String("1"), DisplayName: String("1"), Tags: []model.Tag{{Scope: String("1"), Tag: String("1")}}} + tests := []struct { + name string + iapbs IpAddressPoolBlockSubnet + want data.DataValue + }{ + {"1", p, v}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.iapbs.Value(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIpAddressPoolBlockSubnetsToComparable(t *testing.T) { + type args struct { + iapbs []*model.IpAddressPoolBlockSubnet + } + tests := []struct { + name string + args args + want []Comparable + }{ + {"1", args{[]*model.IpAddressPoolBlockSubnet{{Id: String("1")}}}, []Comparable{&IpAddressPoolBlockSubnet{Id: String("1")}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IpAddressPoolBlockSubnetsToComparable(tt.args.iapbs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("IpAddressPoolBlockSubnetsToComparable() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIpAddressPoolToComparable(t *testing.T) { + type args struct { + iap *model.IpAddressPool + } + tests := []struct { + name string + args args + want Comparable + }{ + {"1", args{&model.IpAddressPool{Id: String("1")}}, &IpAddressPool{Id: String("1")}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IpAddressPoolToComparable(tt.args.iap); !reflect.DeepEqual(got, tt.want) { + t.Errorf("IpAddressPoolToComparable() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIpAddressPool_Key(t *testing.T) { + tests := []struct { + name string + iap IpAddressPool + want string + }{ + {"1", IpAddressPool{Id: String("1")}, "1"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.iap.Key(); got != tt.want { + t.Errorf("Key() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIpAddressPool_Value(t *testing.T) { + m := model.IpAddressPool{Id: String("1"), DisplayName: String("1"), Tags: []model.Tag{{Scope: String("1"), Tag: String("1")}}} + v, _ := m.GetDataValue__() + p := IpAddressPool{Id: String("1"), DisplayName: String("1"), Tags: []model.Tag{{Scope: String("1"), Tag: String("1")}}} + tests := []struct { + name string + iap IpAddressPool + want data.DataValue + }{ + {"1", p, v}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.iap.Value(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/nsx/services/ippool/fake_test.go b/pkg/nsx/services/ippool/fake_test.go new file mode 100644 index 000000000..fa55d3c9c --- /dev/null +++ b/pkg/nsx/services/ippool/fake_test.go @@ -0,0 +1,97 @@ +package ippool + +import ( + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/config" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type fakeQueryClient struct { +} + +type fakeProjectQueryClient struct { +} + +func (f fakeProjectQueryClient) List(orgIdParam string, projectIdParam string, queryParam string, cursorParam *string, includedFieldsParam *string, pageSizeParam *int64, sortAscendingParam *bool, sortByParam *string) (model.SearchResponse, error) { + return model.SearchResponse{}, nil +} + +func (qIface *fakeQueryClient) List(queryParam string, cursorParam *string, includedFieldsParam *string, pageSizeParam *int64, sortAscendingParam *bool, sortByParam *string) (model.SearchResponse, error) { + cursor := "2" + resultCount := int64(2) + return model.SearchResponse{ + Results: []*data.StructValue{&data.StructValue{}}, + Cursor: &cursor, ResultCount: &resultCount, + }, nil +} + +type fakeProjectInfraClient struct { +} + +func (f fakeProjectInfraClient) Get(orgIdParam string, projectIdParam string, basePathParam *string, filterParam *string, typeFilterParam *string) (model.Infra, error) { + return model.Infra{}, nil +} + +func (f fakeProjectInfraClient) Patch(orgIdParam string, projectIdParam string, infraParam model.Infra, enforceRevisionCheckParam *bool) error { + return nil +} + +type fakeRealizedEntitiesClient struct { +} + +func (f fakeRealizedEntitiesClient) List(_ string, _ string, _ string, _ *string) (model.GenericPolicyRealizedResourceListResult, error) { + a := model.GenericPolicyRealizedResourceListResult{ + Results: []model.GenericPolicyRealizedResource{ + { + EntityType: String("IpBlockSubnet"), + ExtendedAttributes: []model.AttributeVal{ + {Key: String("cidr"), Values: []string{"1.1.1.1/24"}}, + }, + }, + }, + } + return a, nil +} + +func fakeService() *IPPoolService { + c := nsx.NewConfig("localhost", "1", "1", []string{}, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, nil, nil, []string{}) + cluster, _ := nsx.NewCluster(c) + rc, _ := cluster.NewRestConnector() + ipPoolStore := &IPPoolStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBindingType(), + }} + ipPoolBlockSubnetStore := &IPPoolBlockSubnetStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBlockSubnetBindingType(), + }} + + service := &IPPoolService{ + Service: common.Service{ + NSXClient: &nsx.Client{ + QueryClient: &fakeQueryClient{}, + RestConnector: rc, + RealizedEntitiesClient: &fakeRealizedEntitiesClient{}, + ProjectInfraClient: &fakeProjectInfraClient{}, + NsxConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + NSXConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + ipPoolStore: ipPoolStore, + ipPoolBlockSubnetStore: ipPoolBlockSubnetStore, + } + return service +} diff --git a/pkg/nsx/services/ippool/ippool.go b/pkg/nsx/services/ippool/ippool.go new file mode 100644 index 000000000..78275b198 --- /dev/null +++ b/pkg/nsx/services/ippool/ippool.go @@ -0,0 +1,285 @@ +package ippool + +import ( + "fmt" + "sync" + "time" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/util" +) + +var ( + log = logger.Log + MarkedForDelete = true + EnforceRevisionCheckParam = false + ResourceTypeIPPool = common.ResourceTypeIPPool + ResourceTypeIPPoolBlockSubnet = common.ResourceTypeIPPoolBlockSubnet + NewConverter = common.NewConverter +) + +type IPPoolService struct { + common.Service + ipPoolStore *IPPoolStore + ipPoolBlockSubnetStore *IPPoolBlockSubnetStore + ExhaustedIPBlock []string +} + +func InitializeIPPool(service common.Service) (*IPPoolService, error) { + wg := sync.WaitGroup{} + wgDone := make(chan bool) + fatalErrors := make(chan error) + + wg.Add(2) + + ipPoolService := &IPPoolService{Service: service} + ipPoolService.ipPoolStore = &IPPoolStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBindingType(), + }} + ipPoolService.ipPoolBlockSubnetStore = &IPPoolBlockSubnetStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBlockSubnetBindingType(), + }} + + tags := []model.Tag{ + {Scope: String(common.TagScopeIPPoolCRUID)}, + } + go ipPoolService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeIPPool, tags, ipPoolService.ipPoolStore) + go ipPoolService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeIPPoolBlockSubnet, tags, ipPoolService.ipPoolBlockSubnetStore) + + go func() { + wg.Wait() + close(wgDone) + }() + select { + case <-wgDone: + break + case err := <-fatalErrors: + close(fatalErrors) + return ipPoolService, err + } + return ipPoolService, nil +} + +func (service *IPPoolService) CreateOrUpdateIPPool(obj *v1alpha2.IPPool) (bool, bool, error) { + nsxIPPool, nsxIPSubnets := service.BuildIPPool(obj) + if len(obj.Spec.Subnets) != 0 && len(nsxIPSubnets) == 0 { + err := util.NoEffectiveOption{Desc: "no valid ip block for ippool"} + return false, false, err + } + for _, ipSubnet := range nsxIPSubnets { + if ipSubnet.IpBlockPath == nil || *ipSubnet.IpBlockPath == "" { + return false, false, util.IPBlockAllExhaustedError{Desc: "all ip blocks are exhausted"} + } + } + existingIPPool, existingIPSubnets, err := service.indexedIPPoolAndIPPoolSubnets(obj.UID) + if err != nil { + log.Error(err, "failed to get ip pool and ip pool subnets by UID", "UID", obj.UID) + return false, false, err + } + log.V(1).Info("existing ippool and ip subnets", "existingIPPool", existingIPPool, "existingIPSubnets", existingIPSubnets) + ipPoolSubnetsUpdated := false + ipPoolUpdated := common.CompareResource(IpAddressPoolToComparable(existingIPPool), IpAddressPoolToComparable(nsxIPPool)) + changed, stale := common.CompareResources(IpAddressPoolBlockSubnetsToComparable(existingIPSubnets), IpAddressPoolBlockSubnetsToComparable(nsxIPSubnets)) + changedIPSubnets, staleIPSubnets := ComparableToIpAddressPoolBlockSubnets(changed), ComparableToIpAddressPoolBlockSubnets(stale) + for i := len(staleIPSubnets) - 1; i >= 0; i-- { + staleIPSubnets[i].MarkedForDelete = &MarkedForDelete + } + finalIPSubnets := append(changedIPSubnets, staleIPSubnets...) + if len(finalIPSubnets) > 0 { + ipPoolSubnetsUpdated = true + } + + if err := service.Apply(nsxIPPool, finalIPSubnets, ipPoolUpdated, ipPoolSubnetsUpdated); err != nil { + return false, false, err + } + + realizedSubnets, subnetCidrUpdated, e := service.AcquireRealizedSubnetIP(obj) + if e != nil { + return false, false, e + } + obj.Status.Subnets = realizedSubnets + return subnetCidrUpdated, ipPoolSubnetsUpdated, nil +} + +func (service *IPPoolService) Apply(nsxIPPool *model.IpAddressPool, nsxIPSubnets []*model.IpAddressPoolBlockSubnet, IPPoolUpdated bool, IPPoolSubnetsUpdated bool) error { + if !(IPPoolUpdated || IPPoolSubnetsUpdated) { + return nil + } + infraIPPool, err := service.WrapHierarchyIPPool(nsxIPPool, nsxIPSubnets) + if err != nil { + return err + } + // Get IPPool Type from nsxIPPool + IPPoolType := common.IPPoolTypePrivate + for _, tag := range nsxIPPool.Tags { + if *tag.Scope == common.TagScopeIPPoolCRType { + IPPoolType = *tag.Tag + break + } + } + + if IPPoolType == common.IPPoolTypePrivate { + ns := service.GetIPPoolNamespace(nsxIPPool) + VPCInfo := commonctl.ServiceMediator.ListVPCInfo(ns) + if len(VPCInfo) == 0 { + err = util.NoEffectiveOption{Desc: "no valid org and project for ippool"} + } else { + err = service.NSXClient.ProjectInfraClient.Patch(VPCInfo[0].OrgID, VPCInfo[0].ProjectID, *infraIPPool, + &EnforceRevisionCheckParam) + } + } else if IPPoolType == common.IPPoolTypePublic { + err = service.NSXClient.InfraClient.Patch(*infraIPPool, &EnforceRevisionCheckParam) + } else { + err = util.NoEffectiveOption{Desc: "not valid IPPool type"} + } + if err != nil { + return err + } + if IPPoolUpdated { + err = service.ipPoolStore.Apply(nsxIPPool) + if err != nil { + return err + } + } + if IPPoolSubnetsUpdated { + err = service.ipPoolBlockSubnetStore.Apply(nsxIPSubnets) + if err != nil { + return err + } + } + log.V(1).Info("successfully created or updated ippool and ip subnets", "nsxIPPool", nsxIPPool) + return nil +} + +func (service *IPPoolService) AcquireRealizedSubnetIP(obj *v1alpha2.IPPool) ([]v1alpha2.SubnetResult, bool, error) { + realizedSubnets := []v1alpha2.SubnetResult{} + subnetCidrUpdated := false + for _, subnetRequest := range obj.Spec.Subnets { + // check if the subnet is already realized + realized := false + realizedSubnet := v1alpha2.SubnetResult{Name: subnetRequest.Name} + for _, statusSubnet := range obj.Status.Subnets { + if statusSubnet.Name == subnetRequest.Name && statusSubnet.CIDR != "" { + realizedSubnet.CIDR = statusSubnet.CIDR + realized = true + break + } + } + if !realized { + cidr, err := service.acquireCidr(obj, &subnetRequest, common.RealizeMaxRetries) + if err != nil { + return nil, subnetCidrUpdated, err + } + if cidr != "" { + subnetCidrUpdated = true + } + realizedSubnet.CIDR = cidr + } + realizedSubnets = append(realizedSubnets, realizedSubnet) + } + return realizedSubnets, subnetCidrUpdated, nil +} + +func (service *IPPoolService) DeleteIPPool(obj interface{}) error { + var err error + var nsxIPPool *model.IpAddressPool + nsxIPSubnets := make([]*model.IpAddressPoolBlockSubnet, 0) + switch o := obj.(type) { + case *v1alpha2.IPPool: + nsxIPPool, nsxIPSubnets = service.BuildIPPool(o) + if err != nil { + log.Error(err, "failed to build ip pool", "IPPool", o) + return err + } + case types.UID: + nsxIPPool, nsxIPSubnets, err = service.indexedIPPoolAndIPPoolSubnets(o) + if err != nil { + log.Error(err, "failed to get ip pool and ip pool subnets by UID", "UID", o) + return err + } + } + nsxIPPool.MarkedForDelete = &MarkedForDelete + for i := len(nsxIPSubnets) - 1; i >= 0; i-- { + nsxIPSubnets[i].MarkedForDelete = &MarkedForDelete + } + if err := service.Apply(nsxIPPool, nsxIPSubnets, true, true); err != nil { + return err + } + log.V(1).Info("successfully deleted nsxIPPool", "nsxIPPool", nsxIPPool) + return nil +} + +func (service *IPPoolService) acquireCidr(obj *v1alpha2.IPPool, subnetRequest *v1alpha2.SubnetRequest, retry int) (string, error) { + intentPath := service.buildIPSubnetIntentPath(obj, subnetRequest) + if intentPath == "" { + return "", fmt.Errorf("failed to build intent path for ip pool %s, subnetRequest %s", obj.Name, subnetRequest.Name) + } + VPCInfo := commonctl.ServiceMediator.ListVPCInfo(obj.Namespace) + var err error + if len(VPCInfo) == 0 { + err = util.NoEffectiveOption{Desc: "no effective org and project for ippool"} + return "", err + } + m, err := service.NSXClient.RealizedEntitiesClient.List(VPCInfo[0].OrgID, VPCInfo[0].ProjectID, intentPath, nil) + if err != nil { + return "", err + } + for _, realizedEntity := range m.Results { + if *realizedEntity.EntityType == "IpBlockSubnet" { + for _, attr := range realizedEntity.ExtendedAttributes { + if *attr.Key == "cidr" { + cidr := attr.Values[0] + log.V(1).Info("successfully realized ippool subnet from ipblock", "subnetRequest.Name", subnetRequest.Name, "cidr", cidr) + return cidr, nil + } + } + } + } + if retry > 0 { + log.V(1).Info("failed to acquire subnet cidr, retrying...", "subnet request", subnetRequest, "retry", retry) + time.Sleep(30 * time.Second) + cidr, e := service.acquireCidr(obj, subnetRequest, retry-1) + return cidr, e + } else { + log.V(1).Info("failed to acquire subnet cidr after multiple retries", "subnet request", subnetRequest) + return "", nil + } +} + +func (service *IPPoolService) ListIPPoolID() sets.String { + ipPoolSet := service.ipPoolStore.ListIndexFuncValues(common.TagScopeIPPoolCRUID) + ipPoolSubnetSet := service.ipPoolBlockSubnetStore.ListIndexFuncValues(common.TagScopeIPPoolCRUID) + return ipPoolSet.Union(ipPoolSubnetSet) +} + +// GetIPPoolNamespace Get IPPool's namespace by tags +func (service *IPPoolService) GetIPPoolNamespace(nsxIPPool *model.IpAddressPool) string { + for _, tag := range nsxIPPool.Tags { + if *tag.Scope == common.TagScopeNamespace { + return *tag.Tag + } + } + return "" +} + +func (service *IPPoolService) Cleanup() error { + uids := service.ListIPPoolID() + log.Info("cleaning up ippool", "count", len(uids)) + for uid := range uids { + err := service.DeleteIPPool(types.UID(uid)) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/nsx/services/ippool/ippool_test.go b/pkg/nsx/services/ippool/ippool_test.go new file mode 100644 index 000000000..db7981df7 --- /dev/null +++ b/pkg/nsx/services/ippool/ippool_test.go @@ -0,0 +1,292 @@ +package ippool + +import ( + "fmt" + "reflect" + "sync" + "testing" + + "github.com/agiledragon/gomonkey/v2" + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/mediator" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +func TestIPPoolService_ListIPPoolID(t *testing.T) { + ipPoolService := fakeService() + p := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + _ = ipPoolService.ipPoolStore.Apply(p) + + tests := []struct { + name string + want sets.String + }{ + {"test", sets.NewString("1")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, ipPoolService.ListIPPoolID(), "ListIPPoolID()") + }) + } +} + +func TestIPPoolService_acquireCidr(t *testing.T) { + vpcCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeVPCCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: vpcCacheIndexer, + BindingType: model.VpcBindingType(), + } + vpcStore := &vpc.VPCStore{ResourceStore: resourceStore} + commonctl.ServiceMediator.VPCService = &vpc.VPCService{VpcStore: vpcStore} + patches := gomonkey.ApplyMethod(reflect.TypeOf(vpcStore), "GetVPCsByNamespace", func(_ *vpc.VPCStore, ns string) []model.Vpc { + id := "vpc-1" + return []model.Vpc{{Path: common.String("/orgs/default/projects/project-1/vpcs/vpc-1"), Id: &id}} + }) + defer patches.Reset() + + ipPoolService := fakeService() + + type args struct { + obj *v1alpha2.IPPool + subnetRequest *v1alpha2.SubnetRequest + } + tests := []struct { + name string + args args + want string + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{obj: &v1alpha2.IPPool{}, subnetRequest: &v1alpha2.SubnetRequest{}}, "1.1.1.1/24", assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ipPoolService.acquireCidr(tt.args.obj, tt.args.subnetRequest, 3) + if !tt.wantErr(t, err, fmt.Sprintf("acquireCidr(%v, %v)", tt.args.obj, tt.args.subnetRequest)) { + return + } + assert.Equalf(t, tt.want, got, "acquireCidr(%v, %v)", tt.args.obj, tt.args.subnetRequest) + }) + } +} + +func TestIPPoolService_DeleteIPPool(t *testing.T) { + service := fakeService() + iap := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + iapbs := []*model.IpAddressPoolBlockSubnet{ + {Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}}} + + patch := gomonkey.ApplyMethod(reflect.TypeOf(service), "BuildIPPool", func(service *IPPoolService, IPPool *v1alpha2.IPPool) (*model. + IpAddressPool, + []*model.IpAddressPoolBlockSubnet) { + return iap, iapbs + }) + patch.ApplyMethod(reflect.TypeOf(service), "Apply", func(service *IPPoolService, nsxIPPool *model.IpAddressPool, + nsxIPSubnets []*model.IpAddressPoolBlockSubnet, IPPoolUpdated bool, IPPoolSubnetsUpdated bool) error { + return nil + }) + defer patch.Reset() + + ipPool := &v1alpha2.IPPool{} + + t.Run("1", func(t *testing.T) { + err := service.DeleteIPPool(ipPool) + assert.NoError(t, err, "DeleteIPPool(%v)", ipPool) + }) +} + +func TestIPPoolService_AcquireRealizedSubnetIP(t *testing.T) { + vpcCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeVPCCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: vpcCacheIndexer, + BindingType: model.VpcBindingType(), + } + vpcStore := &vpc.VPCStore{ResourceStore: resourceStore} + commonctl.ServiceMediator.VPCService = &vpc.VPCService{VpcStore: vpcStore} + patches := gomonkey.ApplyMethod(reflect.TypeOf(vpcStore), "GetVPCsByNamespace", func(_ *vpc.VPCStore, ns string) []model.Vpc { + id := "vpc-1" + return []model.Vpc{{Path: common.String("/orgs/default/projects/project-1/vpcs/vpc-1"), Id: &id}} + }) + defer patches.Reset() + ipPoolService := fakeService() + + ipPool2 := &v1alpha2.IPPool{ + Spec: v1alpha2.IPPoolSpec{ + Subnets: []v1alpha2.SubnetRequest{ + { + Name: "subnet1", + }, + }, + }, + Status: v1alpha2.IPPoolStatus{ + Subnets: []v1alpha2.SubnetResult{ + { + Name: "subnet1", + }, + }, + }, + } + + result := []v1alpha2.SubnetResult{ + { + Name: "subnet1", + CIDR: "1.1.1.1/24", + }, + } + + t.Run("1", func(t *testing.T) { + got, got1, err := ipPoolService.AcquireRealizedSubnetIP(ipPool2) + assert.NoError(t, err, "AcquireRealizedSubnetIP(%v)", ipPool2) + assert.Equalf(t, result, got, "AcquireRealizedSubnetIP(%v)", ipPool2) + assert.Equalf(t, true, got1, "AcquireRealizedSubnetIP(%v)", ipPool2) + }) +} + +func TestIPPoolService_CRUDResource(t *testing.T) { + vpcCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeVPCCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: vpcCacheIndexer, + BindingType: model.VpcBindingType(), + } + vpcStore := &vpc.VPCStore{ResourceStore: resourceStore} + commonctl.ServiceMediator.VPCService = &vpc.VPCService{VpcStore: vpcStore} + patches := gomonkey.ApplyMethod(reflect.TypeOf(vpcStore), "GetVPCsByNamespace", func(_ *vpc.VPCStore, ns string) []model.Vpc { + id := "vpc-1" + return []model.Vpc{{Path: common.String("/orgs/default/projects/project-1/vpcs/vpc-1"), Id: &id}} + }) + defer patches.Reset() + service := fakeService() + iap := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + iapbs := []*model.IpAddressPoolBlockSubnet{ + {Id: String("1"), DisplayName: String("1"), Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), Tag: String("1")}}}} + + ipPoolStore := &IPPoolStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBindingType(), + }} + ipPoolBlockSubnetStore := &IPPoolBlockSubnetStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBlockSubnetBindingType(), + }} + patch := gomonkey.ApplyMethod(reflect.TypeOf(ipPoolStore), "Apply", func(_ *IPPoolStore, nsxIPPool interface{}) error { + return nil + }) + patch.ApplyMethod(reflect.TypeOf(ipPoolBlockSubnetStore), "Apply", func(_ *IPPoolBlockSubnetStore, _ interface{}) error { + return nil + }) + defer patch.Reset() + + t.Run("1", func(t *testing.T) { + err := service.Apply(iap, iapbs, true, true) + assert.NoError(t, err, "Apply(%v)(%v)", iap, iapbs) + }) +} + +func TestIPPoolService_CreateOrUpdateIPPool(t *testing.T) { + service := fakeService() + ipPool2 := &v1alpha2.IPPool{ + Spec: v1alpha2.IPPoolSpec{ + Subnets: []v1alpha2.SubnetRequest{ + { + Name: "subnet1", + }, + }, + Type: common.IPPoolTypePrivate, + }, + Status: v1alpha2.IPPoolStatus{ + Subnets: []v1alpha2.SubnetResult{ + { + Name: "subnet1", + }, + }, + }, + } + + var vpcinfolist = []common.VPCResourceInfo{ + {OrgID: "1", VPCID: "1", ProjectID: "1", ID: "1", ParentID: "1"}, + } + md := mediator.ServiceMediator{} + patch := gomonkey.ApplyMethod(reflect.TypeOf(&md), "ListVPCInfo", func(serviceMediator *mediator.ServiceMediator, + ns string) []common.VPCResourceInfo { + return vpcinfolist + }) + defer patch.Reset() + + p := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + ipPoolStore := &IPPoolStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBindingType(), + }} + ipPoolStore.Apply(p) + + iapbs := []*model.IpAddressPoolBlockSubnet{ + {Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}}} + ipPoolBlockSubnetStore := &IPPoolBlockSubnetStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBlockSubnetBindingType(), + }} + ipPoolBlockSubnetStore.Apply(iapbs) + var vpcinfo = []model.Vpc{ + {PrivateIpv4Blocks: []string{"/infra/ip-blocks/block-test"}}, + } + vpcCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeVPCCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: vpcCacheIndexer, + BindingType: model.VpcBindingType(), + } + vpcStore := &vpc.VPCStore{ResourceStore: resourceStore} + commonctl.ServiceMediator.VPCService = &vpc.VPCService{VpcStore: vpcStore} + patch = gomonkey.ApplyMethod(reflect.TypeOf(vpcStore), "GetVPCsByNamespace", func(vpcStore *vpc.VPCStore, + ns string) []model.Vpc { + return vpcinfo + }) + defer patch.Reset() + t.Run("1", func(t *testing.T) { + got, got1, err := service.CreateOrUpdateIPPool(ipPool2) + assert.NoError(t, err, "CreateOrUpdateIPPool(%v)(%v)", got, got1) + }) +} + +func TestInitializeIPPool(t *testing.T) { + service := fakeService() + wg := sync.WaitGroup{} + fatalErrors := make(chan error) + wg.Add(3) + + var tc *bindings.TypeConverter + patches2 := gomonkey.ApplyMethod(reflect.TypeOf(tc), "ConvertToGolang", + func(_ *bindings.TypeConverter, d data.DataValue, b bindings.BindingType) (interface{}, []error) { + mId, mTag, mScope := "11111", "11111", "11111" + m := model.IpAddressPool{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + var j interface{} = m + return j, nil + }) + defer patches2.Reset() + + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeIPPool, nil, service.ipPoolStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, []string{"11111"}, service.ipPoolStore.ListKeys()) +} diff --git a/pkg/nsx/services/ippool/parse.go b/pkg/nsx/services/ippool/parse.go new file mode 100644 index 000000000..8dde41d89 --- /dev/null +++ b/pkg/nsx/services/ippool/parse.go @@ -0,0 +1,30 @@ +package ippool + +import ( + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" +) + +func (service *IPPoolService) GetUnrealizedSubnetNames(obj *v1alpha2.IPPool) []string { + var unrealizedSubnets []string + for _, subnetRequest := range obj.Spec.Subnets { + realized := false + for _, statusSubnet := range obj.Status.Subnets { + if statusSubnet.Name == subnetRequest.Name && statusSubnet.CIDR != "" { + realized = true + break + } + } + if !realized { + unrealizedSubnets = append(unrealizedSubnets, subnetRequest.Name) + } + } + return unrealizedSubnets +} + +func (service *IPPoolService) FullyRealized(obj *v1alpha2.IPPool) bool { + return len(service.GetUnrealizedSubnetNames(obj)) == 0 +} + +func getCluster(service *IPPoolService) string { + return service.NSXConfig.Cluster +} diff --git a/pkg/nsx/services/ippool/parse_test.go b/pkg/nsx/services/ippool/parse_test.go new file mode 100644 index 000000000..0e04e140a --- /dev/null +++ b/pkg/nsx/services/ippool/parse_test.go @@ -0,0 +1,99 @@ +package ippool + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" +) + +func TestIPPoolService_FullyRealized(t *testing.T) { + ipPoolService := fakeService() + ipPool := &v1alpha2.IPPool{ + Spec: v1alpha2.IPPoolSpec{ + Subnets: []v1alpha2.SubnetRequest{ + { + Name: "subnet1", + }, + }, + }, + Status: v1alpha2.IPPoolStatus{ + Subnets: []v1alpha2.SubnetResult{ + { + Name: "subnet1", + CIDR: "1.1.1/24", + }, + }, + }, + } + + ipPool2 := &v1alpha2.IPPool{ + Spec: v1alpha2.IPPoolSpec{ + Subnets: []v1alpha2.SubnetRequest{ + { + Name: "subnet1", + }, + }, + }, + Status: v1alpha2.IPPoolStatus{ + Subnets: []v1alpha2.SubnetResult{ + { + Name: "subnet1", + }, + }, + }, + } + type args struct { + obj *v1alpha2.IPPool + } + tests := []struct { + name string + args args + want bool + }{ + {"fully realized", args{ipPool}, true}, + {"not fully realized", args{ipPool2}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, ipPoolService.FullyRealized(tt.args.obj), "FullyRealized(%v)", tt.args.obj) + }) + } +} + +func TestIPPoolService_GetUnrealizedSubnetNames(t *testing.T) { + ipPoolService := fakeService() + + ipPool2 := &v1alpha2.IPPool{ + Spec: v1alpha2.IPPoolSpec{ + Subnets: []v1alpha2.SubnetRequest{ + { + Name: "subnet1", + }, + }, + }, + Status: v1alpha2.IPPoolStatus{ + Subnets: []v1alpha2.SubnetResult{ + { + Name: "subnet1", + }, + }, + }, + } + type args struct { + obj *v1alpha2.IPPool + } + tests := []struct { + name string + args args + want []string + }{ + {"1", args{ipPool2}, []string{"subnet1"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, ipPoolService.GetUnrealizedSubnetNames(tt.args.obj), "GetUnrealizedSubnetNames(%v)", tt.args.obj) + }) + } +} diff --git a/pkg/nsx/services/ippool/store.go b/pkg/nsx/services/ippool/store.go new file mode 100644 index 000000000..9597db8d9 --- /dev/null +++ b/pkg/nsx/services/ippool/store.go @@ -0,0 +1,144 @@ +package ippool + +import ( + "errors" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/types" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +func keyFunc(obj interface{}) (string, error) { + switch v := obj.(type) { + case model.IpAddressPool: + return *v.Id, nil + case model.IpAddressPoolBlockSubnet: + return *v.Id, nil + case model.GenericPolicyRealizedResource: + return *v.Id, nil + default: + return "", errors.New("keyFunc doesn't support unknown type") + } +} + +func indexFunc(obj interface{}) ([]string, error) { + res := make([]string, 0, 5) + switch v := obj.(type) { + case model.IpAddressPoolBlockSubnet: + return filterTag(v.Tags), nil + case model.IpAddressPool: + return filterTag(v.Tags), nil + default: + return res, errors.New("indexFunc doesn't support unknown type") + } +} + +var filterTag = func(v []model.Tag) []string { + res := make([]string, 0, 5) + for _, tag := range v { + if *tag.Scope == common.TagScopeIPPoolCRUID { + res = append(res, *tag.Tag) + } + } + return res +} + +type IPPoolStore struct { + common.ResourceStore +} + +type IPPoolBlockSubnetStore struct { + common.ResourceStore +} + +func ipPoolAssertion(i interface{}) interface{} { + return i.(model.IpAddressPool) +} + +func ipPoolBlockSubnetAssertion(i interface{}) interface{} { + return i.(model.IpAddressPoolBlockSubnet) +} + +func (ipPoolStore *IPPoolStore) Apply(i interface{}) error { + ipPool := i.(*model.IpAddressPool) + if ipPool.MarkedForDelete != nil && *ipPool.MarkedForDelete { + err := ipPoolStore.Delete(*ipPool) + log.V(1).Info("delete ipPool from store", "ipPool", ipPool) + if err != nil { + return err + } + } else { + err := ipPoolStore.Add(*ipPool) + log.V(1).Info("add ipPool to store", "ipPool", ipPool) + if err != nil { + return err + } + } + return nil +} + +func (ipPoolBlockSubnetStore *IPPoolBlockSubnetStore) Apply(i interface{}) error { + ipPoolBlockSubnets := i.([]*model.IpAddressPoolBlockSubnet) + for _, ipPoolBlockSubnet := range ipPoolBlockSubnets { + if ipPoolBlockSubnet.MarkedForDelete != nil && *ipPoolBlockSubnet.MarkedForDelete { + err := ipPoolBlockSubnetStore.Delete(*ipPoolBlockSubnet) + log.V(1).Info("delete ipPoolBlockSubnet from store", "ipPoolBlockSubnet", ipPoolBlockSubnet) + if err != nil { + return err + } + } else { + err := ipPoolBlockSubnetStore.Add(*ipPoolBlockSubnet) + log.V(1).Info("add ipPoolBlockSubnet to store", "ipPoolBlockSubnet", ipPoolBlockSubnet) + if err != nil { + return err + } + } + } + return nil +} + +func (service *IPPoolService) indexedIPPoolAndIPPoolSubnets(uid types.UID) (*model.IpAddressPool, []*model.IpAddressPoolBlockSubnet, error) { + nsxIPPool, err := service.ipPoolStore.GetByIndex(uid) + if err != nil { + return nil, nil, err + } + nsxIPPoolSubnets, err := service.ipPoolBlockSubnetStore.GetByIndex(uid) + if err != nil { + return nil, nil, err + } + return nsxIPPool, nsxIPPoolSubnets, nil +} + +func (ipPoolBlockSubnetStore *IPPoolBlockSubnetStore) GetByIndex(uid types.UID) ([]*model.IpAddressPoolBlockSubnet, error) { + nsxIPSubnets := make([]*model.IpAddressPoolBlockSubnet, 0) + indexResults, err := ipPoolBlockSubnetStore.ResourceStore.ByIndex(common.TagScopeIPPoolCRUID, string(uid)) + if err != nil { + log.Error(err, "failed to get ip subnets", "UID", string(uid)) + return nil, err + } + if len(indexResults) == 0 { + log.Info("did not get ip subnets with index", "UID", string(uid)) + } + for _, ipSubnet := range indexResults { + t := ipSubnet.(model.IpAddressPoolBlockSubnet) + nsxIPSubnets = append(nsxIPSubnets, &t) + } + return nsxIPSubnets, nil +} + +func (ipPoolStore *IPPoolStore) GetByIndex(uid types.UID) (*model.IpAddressPool, error) { + nsxIPPool := &model.IpAddressPool{} + indexResults, err := ipPoolStore.ResourceStore.ByIndex(common.TagScopeIPPoolCRUID, string(uid)) + if err != nil { + log.Error(err, "failed to get ip pool", "UID", string(uid)) + return nil, err + } + if len(indexResults) > 0 { + t := indexResults[0].(model.IpAddressPool) + nsxIPPool = &t + } else { + log.Info("did not get ip pool with index", "UID", string(uid)) + } + return nsxIPPool, nil +} diff --git a/pkg/nsx/services/ippool/store_test.go b/pkg/nsx/services/ippool/store_test.go new file mode 100644 index 000000000..bc337672f --- /dev/null +++ b/pkg/nsx/services/ippool/store_test.go @@ -0,0 +1,270 @@ +package ippool + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +func TestIPPoolBlockSubnetStore_CRUDResource(t *testing.T) { + ipPoolBlockSubnetCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: ipPoolBlockSubnetCacheIndexer, + BindingType: model.IpAddressPoolBlockSubnetBindingType(), + } + ipPoolBlockSubnetStore := &IPPoolBlockSubnetStore{ResourceStore: resourceStore} + type args struct { + i interface{} + } + tests := []struct { + name string + args args + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{i: []*model.IpAddressPoolBlockSubnet{{Id: String("1")}}}, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, ipPoolBlockSubnetStore.Apply(tt.args.i), fmt.Sprintf("Apply(%v)", tt.args.i)) + }) + } +} + +func TestIPPoolStore_GetByIndex(t *testing.T) { + p := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + ipPoolStore := &IPPoolStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBindingType(), + }} + ipPoolStore.Apply(p) + type args struct { + uid types.UID + } + tests := []struct { + name string + args args + want *model.IpAddressPool + wantErr bool + }{ + {"1", args{uid: "1"}, &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), Tag: String("1")}}}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ipPoolStore.GetByIndex(tt.args.uid) + if (err != nil) != tt.wantErr { + t.Errorf("indexedIPPool() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("indexedIPPool() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIPPoolBlockSubnetStore_GetByIndex(t *testing.T) { + p := []*model.IpAddressPoolBlockSubnet{ + {Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}}} + ipPoolBlockSubnetStore := &IPPoolBlockSubnetStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}), + BindingType: model.IpAddressPoolBlockSubnetBindingType(), + }} + ipPoolBlockSubnetStore.Apply(p) + type args struct { + uid types.UID + } + tests := []struct { + name string + args args + want []*model.IpAddressPoolBlockSubnet + wantErr bool + }{ + {"1", args{uid: "1"}, []*model.IpAddressPoolBlockSubnet{{Id: String("1"), DisplayName: String("1"), Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), Tag: String("1")}}}}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ipPoolBlockSubnetStore.GetByIndex(tt.args.uid) + if (err != nil) != tt.wantErr { + t.Errorf("indexedIPPoolSubnets() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("indexedIPPoolSubnets() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIPPoolStore_CRUDResource(t *testing.T) { + ipPoolCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeIPPoolCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: ipPoolCacheIndexer, + BindingType: model.IpAddressPoolBindingType(), + } + ipPoolStore := &IPPoolStore{ResourceStore: resourceStore} + type args struct { + i interface{} + } + tests := []struct { + name string + args args + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{i: &model.IpAddressPool{Id: String("1")}}, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, ipPoolStore.Apply(tt.args.i), fmt.Sprintf("Apply(%v)", tt.args.i)) + }) + } +} + +func Test_indexFunc(t *testing.T) { + mId, mTag, mScope := "11111", "11111", common.TagScopeIPPoolCRUID + m := model.IpAddressPool{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + r := model.IpAddressPoolBlockSubnet{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + t.Run("1", func(t *testing.T) { + got, _ := indexFunc(m) + if !reflect.DeepEqual(got, []string{"11111"}) { + t.Errorf("indexFunc() = %v, want %v", got, model.Tag{Tag: &mTag, Scope: &mScope}) + } + }) + t.Run("2", func(t *testing.T) { + got, _ := indexFunc(r) + if !reflect.DeepEqual(got, []string{"11111"}) { + t.Errorf("indexFunc() = %v, want %v", got, model.Tag{Tag: &mTag, Scope: &mScope}) + } + }) +} + +func Test_ipPoolAssertion(t *testing.T) { + mId, mTag, mScope := "11111", "11111", common.TagScopeIPPoolCRUID + m := model.IpAddressPool{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + type args struct { + i interface{} + } + tests := []struct { + name string + args args + want interface{} + }{ + {"1", args{i: m}, m}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ipPoolAssertion(tt.args.i); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ipPoolAssertion() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_ipPoolBlockSubnetAssertion(t *testing.T) { + mId, mTag, mScope := "11111", "11111", common.TagScopeIPPoolCRUID + r := model.IpAddressPoolBlockSubnet{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + type args struct { + i interface{} + } + tests := []struct { + name string + args args + want interface{} + }{ + {"1", args{i: r}, r}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ipPoolBlockSubnetAssertion(tt.args.i); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ipPoolBlockSubnetAssertion() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_keyFunc(t *testing.T) { + Id := "11111" + g := model.IpAddressPool{Id: &Id} + s := model.IpAddressPoolBlockSubnet{Id: &Id} + t.Run("1", func(t *testing.T) { + got, _ := keyFunc(s) + if got != "11111" { + t.Errorf("keyFunc() = %v, want %v", got, "11111") + } + }) + t.Run("2", func(t *testing.T) { + got, _ := keyFunc(g) + if got != "11111" { + t.Errorf("keyFunc() = %v, want %v", got, "11111") + } + }) +} + +func TestIPPoolService_indexedIPPoolAndIPPoolSubnets1(t *testing.T) { + ipPoolService := fakeService() + p := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + ipPoolService.ipPoolStore.Apply(p) + + iapbs := []*model.IpAddressPoolBlockSubnet{ + {Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}}} + ipPoolService.ipPoolBlockSubnetStore.Apply(iapbs) + + type args struct { + uid types.UID + } + tests := []struct { + name string + args args + want []*model.IpAddressPoolBlockSubnet + want2 *model.IpAddressPool + wantErr bool + }{ + { + "1", + args{uid: "1"}, + []*model.IpAddressPoolBlockSubnet{{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), Tag: String("1")}}}}, + &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), Tag: String("1")}}}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got2, _ := ipPoolService.indexedIPPoolAndIPPoolSubnets(tt.args.uid) + if !reflect.DeepEqual(got, tt.want2) { + t.Errorf("indexedIPPool() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got2, tt.want) { + t.Errorf("indexedIPPoolSubnets() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/nsx/services/ippool/wrap.go b/pkg/nsx/services/ippool/wrap.go new file mode 100644 index 000000000..62cc2d62e --- /dev/null +++ b/pkg/nsx/services/ippool/wrap.go @@ -0,0 +1,80 @@ +package ippool + +import ( + "github.com/openlyinc/pointy" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +func (service *IPPoolService) WrapHierarchyIPPool(iap *model.IpAddressPool, iapbs []*model.IpAddressPoolBlockSubnet) (*model.Infra, error) { + IPSubnetsChildren, err := service.wrapIPSubnets(iapbs) + if err != nil { + return nil, err + } + iap.Children = IPSubnetsChildren + IPPoolChildren, err := service.wrapIPPool(iap) + if err != nil { + return nil, err + } + var infraChildren []*data.StructValue + infraChildren = append(infraChildren, IPPoolChildren...) + + infra, err := service.wrapInfra(infraChildren) + if err != nil { + return nil, err + } + return infra, nil +} + +func (service *IPPoolService) wrapInfra(children []*data.StructValue) (*model.Infra, error) { + // This is the outermost layer of the hierarchy security policy. + // It doesn't need ID field. + infraType := "Infra" + infraObj := model.Infra{ + Children: children, + ResourceType: &infraType, + } + return &infraObj, nil +} + +func (service *IPPoolService) wrapIPSubnets(IPSubnets []*model.IpAddressPoolBlockSubnet) ([]*data.StructValue, error) { + var IPSubnetsChildren []*data.StructValue + for _, IPSubnet := range IPSubnets { + IPSubnet.ResourceType = common.ResourceTypeIPPoolBlockSubnet + dataValue, errs := NewConverter().ConvertToVapi(IPSubnet, model.IpAddressPoolBlockSubnetBindingType()) + if len(errs) > 0 { + return nil, errs[0] + } + childIPSubnet := model.ChildIpAddressPoolSubnet{ + ResourceType: "ChildIpAddressPoolSubnet", + Id: IPSubnet.Id, + MarkedForDelete: IPSubnet.MarkedForDelete, + IpAddressPoolSubnet: dataValue.(*data.StructValue), + } + dataValue, errs = NewConverter().ConvertToVapi(childIPSubnet, model.ChildIpAddressPoolSubnetBindingType()) + if len(errs) > 0 { + return nil, errs[0] + } + IPSubnetsChildren = append(IPSubnetsChildren, dataValue.(*data.StructValue)) + } + return IPSubnetsChildren, nil +} + +func (service *IPPoolService) wrapIPPool(iap *model.IpAddressPool) ([]*data.StructValue, error) { + var IPPoolChildren []*data.StructValue + iap.ResourceType = pointy.String(common.ResourceTypeIPPool) + childIPool := model.ChildIpAddressPool{ + Id: iap.Id, + MarkedForDelete: iap.MarkedForDelete, + ResourceType: "ChildIpAddressPool", + IpAddressPool: iap, + } + dataValue, errs := NewConverter().ConvertToVapi(childIPool, model.ChildIpAddressPoolBindingType()) + if len(errs) > 0 { + return nil, errs[0] + } + IPPoolChildren = append(IPPoolChildren, dataValue.(*data.StructValue)) + return IPPoolChildren, nil +} diff --git a/pkg/nsx/services/ippool/wrap_test.go b/pkg/nsx/services/ippool/wrap_test.go new file mode 100644 index 000000000..1e38bfed6 --- /dev/null +++ b/pkg/nsx/services/ippool/wrap_test.go @@ -0,0 +1,41 @@ +package ippool + +import ( + "testing" + + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +func TestIPPoolService_WrapHierarchyIPPool(t *testing.T) { + Converter := bindings.NewTypeConverter() + Converter.SetMode(bindings.REST) + service := fakeService() + iapbs := []*model.IpAddressPoolBlockSubnet{ + &model.IpAddressPoolBlockSubnet{ + Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), Tag: String("1")}}}} + iap := &model.IpAddressPool{Id: String("1"), DisplayName: String("1"), + Tags: []model.Tag{{Scope: String(common.TagScopeIPPoolCRUID), + Tag: String("1")}}} + + tests := []struct { + name string + want []*data.StructValue + wantErr bool + }{ + {"1", nil, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := service.WrapHierarchyIPPool(iap, iapbs) + if (err != nil) != tt.wantErr { + t.Errorf("WrapHierarchyIPPool() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/pkg/nsx/services/mediator/mediator.go b/pkg/nsx/services/mediator/mediator.go index 3290ee621..a71d12269 100644 --- a/pkg/nsx/services/mediator/mediator.go +++ b/pkg/nsx/services/mediator/mediator.go @@ -1,16 +1,106 @@ package mediator import ( - "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/securitypolicy" + "context" + "fmt" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/node" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnet" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/subnetport" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" + "github.com/vmware-tanzu/nsx-operator/pkg/util" ) +var log = logger.Log + // ServiceMediator We use mediator pattern to wrap all the services, // embed all the services in ServiceMediator, so that we can mediate all the methods of all the services // transparently to the caller, for example, in other packages, we can use ServiceMediator.GetVPCsByNamespace directly. // In startCRDController function, we register the CRDService to the ServiceMediator, since only one controller writes to // its own store and other controllers read from the store, so we don't need lock here. type ServiceMediator struct { - *securitypolicy.SecurityPolicyService *vpc.VPCService + *subnet.SubnetService + *subnetport.SubnetPortService + *node.NodeService +} + +// ListVPCInfo is a common method, extracting the org, the project, and the vpc string from vpc path of the VPC model. +// VPC path looks like "/orgs/default/projects/project-1/vpcs/vpc-1", +// Since other modules only know namespace, this is the only entry point to get org and project. +// Currently, we only support one vpc per namespace, but we may support multiple vpcs per namespace in the future, +// so we return a slice of VPCInfo. +func (serviceMediator *ServiceMediator) ListVPCInfo(ns string) []common.VPCResourceInfo { + var VPCInfoList []common.VPCResourceInfo + vpcs := serviceMediator.GetVPCsByNamespace(ns) // Transparently call the VPCService.GetVPCsByNamespace method + for _, v := range vpcs { + vpcResourceInfo, err := common.ParseVPCResourcePath(*v.Path) + if err != nil { + log.Error(err, "Failed to get vpc info from vpc path", "vpc path", *v.Path) + } + VPCInfoList = append(VPCInfoList, vpcResourceInfo) + } + return VPCInfoList +} + +// This method is used for subnet service since vpc network config contains default subnet size +// and default subnet access mode. +func (m *ServiceMediator) GetVPCNetworkConfigByNamespace(ns string) *vpc.VPCNetworkConfigInfo { + return m.VPCService.GetVPCNetworkConfigByNamespace(ns) +} + +// GetAvailableSubnet returns available Subnet under SubnetSet, and creates Subnet if necessary. +func (serviceMediator *ServiceMediator) GetAvailableSubnet(subnetSet *v1alpha1.SubnetSet) (string, error) { + subnetList := serviceMediator.SubnetStore.GetByIndex(common.TagScopeSubnetSetCRUID, string(subnetSet.GetUID())) + for _, nsxSubnet := range subnetList { + portNums := len(serviceMediator.GetPortsOfSubnet(*nsxSubnet.Id)) + totalIP := int(*nsxSubnet.Ipv4SubnetSize) + if len(nsxSubnet.IpAddresses) > 0 { + // totalIP will be overrided if IpAddresses are specified. + totalIP, _ = util.CalculateIPFromCIDRs(nsxSubnet.IpAddresses) + } + if portNums < totalIP-3 { + return *nsxSubnet.Path, nil + } + } + namespace := &corev1.Namespace{} + namespacedName := types.NamespacedName{ + Name: subnetSet.Namespace, + } + if err := serviceMediator.SubnetService.Client.Get(context.Background(), namespacedName, namespace); err != nil { + return "", err + } + tags := serviceMediator.SubnetService.GenerateSubnetNSTags(subnetSet, string(namespace.UID)) + for k, v := range namespace.Labels { + tags = append(tags, model.Tag{Scope: common.String(k), Tag: common.String(v)}) + } + log.Info("the existing subnets are not available, creating new subnet", "subnetList", subnetList, "subnetSet.Name", subnetSet.Name, "subnetSet.Namespace", subnetSet.Namespace) + return serviceMediator.CreateOrUpdateSubnet(subnetSet, tags) +} + +func (serviceMediator *ServiceMediator) GetPortsOfSubnet(nsxSubnetID string) (ports []model.SegmentPort) { + subnetPortList := serviceMediator.SubnetPortStore.GetByIndex(common.IndexKeySubnetID, nsxSubnetID) + return subnetPortList +} + +func (serviceMediator *ServiceMediator) GetNodeByName(nodeName string) (*model.HostTransportNode, error) { + nodes := serviceMediator.NodeStore.GetByIndex(common.IndexKeyNodeName, nodeName) + if len(nodes) == 0 { + return nil, fmt.Errorf("node %s not found", nodeName) + } + if len(nodes) > 1 { + var nodeIDs []string + for _, node := range nodes { + nodeIDs = append(nodeIDs, *node.Id) + } + return nil, fmt.Errorf("multiple node IDs found for node %s: %v", nodeName, nodeIDs) + } + return &nodes[0], nil } diff --git a/pkg/nsx/services/mediator/mediator_test.go b/pkg/nsx/services/mediator/mediator_test.go new file mode 100644 index 000000000..3ac0efd44 --- /dev/null +++ b/pkg/nsx/services/mediator/mediator_test.go @@ -0,0 +1,30 @@ +package mediator + +import ( + "reflect" + "testing" + + "github.com/agiledragon/gomonkey" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +func TestServiceMediator_GetOrgProject(t *testing.T) { + vpcService := &vpc.VPCService{} + vs := &ServiceMediator{ + VPCService: vpcService, + } + + patches := gomonkey.ApplyMethod(reflect.TypeOf(vpcService), "GetVPCsByNamespace", func(_ *vpc.VPCService, ns string) []model.Vpc { + return []model.Vpc{{Path: common.String("/orgs/default/projects/project-1/vpcs/vpc-1")}} + }) + defer patches.Reset() + + got := vs.ListVPCInfo("ns")[0] + want := common.VPCResourceInfo{OrgID: "default", ProjectID: "project-1", VPCID: "vpc-1", ID: "vpc-1", ParentID: "project-1"} + if !reflect.DeepEqual(got, want) { + t.Errorf("GetOrgProject() = %v, want %v", got, want) + } +} diff --git a/pkg/nsx/services/node/node.go b/pkg/nsx/services/node/node.go new file mode 100644 index 000000000..0810dae90 --- /dev/null +++ b/pkg/nsx/services/node/node.go @@ -0,0 +1,114 @@ +package node + +import ( + "fmt" + "sync" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +var ( + log = logger.Log + ResourceTypeNode = servicecommon.ResourceTypeNode + MarkedForDelete = true +) + +type NodeService struct { + servicecommon.Service + NodeStore *NodeStore +} + +func InitializeNode(service servicecommon.Service) (*NodeService, error) { + wg := sync.WaitGroup{} + wgDone := make(chan bool) + fatalErrors := make(chan error) + + wg.Add(1) + + nodeService := &NodeService{Service: service} + + nodeService.NodeStore = &NodeStore{ + ResourceStore: servicecommon.ResourceStore{ + Indexer: cache.NewIndexer( + keyFunc, + cache.Indexers{ + servicecommon.IndexKeyNodeName: nodeIndexByNodeName, + }, + ), + BindingType: model.HostTransportNodeBindingType(), + }, + } + // TODO: confirm whether we can remove the following intialization because node doesn't have the cluster tag so it's a dry run + go nodeService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeNode, nil, nodeService.NodeStore) + + go func() { + wg.Wait() + close(wgDone) + }() + + select { + case <-wgDone: + break + case err := <-fatalErrors: + close(fatalErrors) + return nodeService, err + } + + return nodeService, nil + +} + +func (service *NodeService) SyncNodeStore(nodeName string, deleted bool) error { + nodes := service.NodeStore.GetByIndex(servicecommon.IndexKeyNodeName, nodeName) + if len(nodes) > 1 { + return fmt.Errorf("multiple nodes found for node name %s", nodeName) + } + // TODO: confirm whether we need to resync the node info from NSX + if len(nodes) == 1 { + log.Info("node alreay cached", "node.Fqdn", nodes[0].NodeDeploymentInfo.Fqdn, "node.Id", *nodes[0].Id) + // updatedNode, err := service.NSXClient.HostTransPortNodesClient.Get("default", "default", nodes[0].Id) + // if err != nil { + // return fmt.Errorf("failed to get HostTransPortNode for node %s: %s", nodeName, err) + // } + // node.NodeStore.Apply(updatedNode) + } + nodeResults, err := service.NSXClient.HostTransPortNodesClient.List("default", "default", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + if err != nil { + return fmt.Errorf("failed to list HostTransportNodes: %s", err) + } + if deleted { + nodes := service.NodeStore.GetByIndex(servicecommon.IndexKeyNodeName, nodeName) + if len(nodes) == 0 { + log.Info("skip deleting node in store because the node is not in store", "nodeName", nodeName) + return nil + } + for _, node := range nodes { + *node.MarkedForDelete = true + service.NodeStore.Apply(node) + } + } + synced := false + for _, node := range nodeResults.Results { + if *node.NodeDeploymentInfo.Fqdn == nodeName { + if deleted { + // Retry until the NSX HostTransportNode is deleted. + return fmt.Errorf("node %s had beed deleted but HostTransportNodes still exists", nodeName) + } + err = service.NodeStore.Apply(&node) + if err != nil { + return fmt.Errorf("failed to sync node %s: %s", nodeName, err) + } + synced = true + break + } + } + if !synced && !deleted { + // Retry until the NSX HostTransportNode is available. + return fmt.Errorf("node %s not found yet in NSX side", nodeName) + } + return nil +} diff --git a/pkg/nsx/services/node/store.go b/pkg/nsx/services/node/store.go new file mode 100644 index 000000000..91a28b1c9 --- /dev/null +++ b/pkg/nsx/services/node/store.go @@ -0,0 +1,72 @@ +package node + +import ( + "errors" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +// NodeStore is a store for node (NSX HostTransportNode) +type NodeStore struct { + common.ResourceStore +} + +func (vs *NodeStore) Apply(i interface{}) error { + if i == nil { + return nil + } + node := i.(*model.HostTransportNode) + if node.MarkedForDelete != nil && *node.MarkedForDelete { + err := vs.Delete(*node) + log.V(1).Info("delete Node from store", "node", node) + if err != nil { + return err + } + } else { + err := vs.Add(*node) + log.V(1).Info("add Node to store", "node", node) + if err != nil { + return err + } + } + return nil +} + +func (nodeStore *NodeStore) GetByKey(key string) *model.HostTransportNode { + var node model.HostTransportNode + obj := nodeStore.ResourceStore.GetByKey(key) + if obj != nil { + node = obj.(model.HostTransportNode) + } + return &node +} + +func (nodeStore *NodeStore) GetByIndex(key string, value string) []model.HostTransportNode { + hostTransportNodes := make([]model.HostTransportNode, 0) + objs := nodeStore.ResourceStore.GetByIndex(key, value) + for _, node := range objs { + hostTransportNodes = append(hostTransportNodes, node.(model.HostTransportNode)) + } + return hostTransportNodes +} + +// keyFunc is used to get the key of a resource, usually, which is the ID of the resource +func keyFunc(obj interface{}) (string, error) { + switch v := obj.(type) { + case model.HostTransportNode: + return *v.Id, nil + default: + return "", errors.New("keyFunc doesn't support unknown type") + } +} + +func nodeIndexByNodeName(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.HostTransportNode: + return []string{*o.NodeDeploymentInfo.Fqdn}, nil + default: + return nil, errors.New("nodeIndexByNodeName doesn't support unknown type") + } +} diff --git a/pkg/nsx/services/nsxserviceaccount/cluster.go b/pkg/nsx/services/nsxserviceaccount/cluster.go index f75033cb3..65687c2f2 100644 --- a/pkg/nsx/services/nsxserviceaccount/cluster.go +++ b/pkg/nsx/services/nsxserviceaccount/cluster.go @@ -71,8 +71,8 @@ func InitializeNSXServiceAccount(service common.Service) (*NSXServiceAccountServ nsxServiceAccountService := &NSXServiceAccountService{Service: service} nsxServiceAccountService.SetUpStore() - go nsxServiceAccountService.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypePrincipalIdentity, nsxServiceAccountService.PrincipalIdentityStore) - go nsxServiceAccountService.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypeClusterControlPlane, nsxServiceAccountService.ClusterControlPlaneStore) + go nsxServiceAccountService.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypePrincipalIdentity, nil, nsxServiceAccountService.PrincipalIdentityStore) + go nsxServiceAccountService.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypeClusterControlPlane, nil, nsxServiceAccountService.ClusterControlPlaneStore) go func() { wg.Wait() close(wgDone) diff --git a/pkg/nsx/services/nsxserviceaccount/store.go b/pkg/nsx/services/nsxserviceaccount/store.go index a112bc2a6..66701f1c2 100644 --- a/pkg/nsx/services/nsxserviceaccount/store.go +++ b/pkg/nsx/services/nsxserviceaccount/store.go @@ -5,6 +5,7 @@ package nsxserviceaccount import ( "errors" + mpmodel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" @@ -16,7 +17,7 @@ type PrincipalIdentityStore struct { common.ResourceStore } -func (s *PrincipalIdentityStore) Operate(i interface{}) error { +func (s *PrincipalIdentityStore) Apply(i interface{}) error { pis := i.(*[]mpmodel.PrincipalIdentity) for _, pi := range *pis { // MP resource doesn't have MarkedForDelete tag. @@ -38,7 +39,7 @@ type ClusterControlPlaneStore struct { common.ResourceStore } -func (s *ClusterControlPlaneStore) Operate(i interface{}) error { +func (s *ClusterControlPlaneStore) Apply(i interface{}) error { pis := i.(*[]model.ClusterControlPlane) for _, pi := range *pis { if pi.MarkedForDelete != nil && *pi.MarkedForDelete { diff --git a/pkg/nsx/services/realizestate/realize_state.go b/pkg/nsx/services/realizestate/realize_state.go new file mode 100644 index 000000000..cc0df841a --- /dev/null +++ b/pkg/nsx/services/realizestate/realize_state.go @@ -0,0 +1,60 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package realizestate + +import ( + "errors" + "fmt" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/retry" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type RealizeStateService struct { + common.Service +} + +type RealizeError struct { +} + +func InitializeRealizeState(service common.Service) *RealizeStateService { + return &RealizeStateService{ + Service: service, + } +} + +func IsRealizeStateError(err error) bool { + return err.Error() == model.GenericPolicyRealizedResource_STATE_ERROR +} + +// CheckRealizeState allows the caller to check realize status of entityType with retries. +// backoff defines the maximum retries and the wait interval between two retries. +func (service *RealizeStateService) CheckRealizeState(backoff wait.Backoff, intentPath, entityType string) error { + vpcInfo, err := common.ParseVPCResourcePath(intentPath) + if err != nil { + return err + } + return retry.OnError(backoff, func(err error) bool { + // Won't retry when realized state is `ERROR`. + return !IsRealizeStateError(err) + }, func() error { + results, err := service.NSXClient.RealizedEntitiesClient.List(vpcInfo.OrgID, vpcInfo.ProjectID, intentPath, nil) + if err != nil { + return err + } + for _, result := range results.Results { + if *result.EntityType != entityType { + continue + } + if *result.State == model.GenericPolicyRealizedResource_STATE_REALIZED { + return nil + } + return errors.New(*result.State) + } + return errors.New(fmt.Sprintf("%s not realized", entityType)) + }) +} diff --git a/pkg/nsx/services/securitypolicy/builder.go b/pkg/nsx/services/securitypolicy/builder.go index 2ef9c41e7..95bada68b 100644 --- a/pkg/nsx/services/securitypolicy/builder.go +++ b/pkg/nsx/services/securitypolicy/builder.go @@ -1,6 +1,7 @@ package securitypolicy import ( + "context" "encoding/json" "errors" "fmt" @@ -9,7 +10,9 @@ import ( "github.com/vmware/vsphere-automation-sdk-go/runtime/data" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" - "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" @@ -26,7 +29,7 @@ const ( MaxMatchExpressionIn int = 1 MaxMatchExpressionInValues int = 5 ClusterTagCount int = 1 - ProjectTagCount int = 1 + NameSpaceTagCount int = 1 ) var ( @@ -34,9 +37,10 @@ var ( Int64 = common.Int64 ) -func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.SecurityPolicy) (*model.SecurityPolicy, *[]model.Group, error) { +func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.SecurityPolicy) (*model.SecurityPolicy, *[]model.Group, *[]ProjectShare, error) { var nsxRules []model.Rule var nsxGroups []model.Group + var projectShares []ProjectShare contains := func(groups []model.Group, group model.Group) bool { for _, a := range groups { @@ -59,7 +63,7 @@ func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.Security policyGroup, policyGroupPath, err := service.buildPolicyGroup(obj) if err != nil { log.Error(err, "failed to build policy group", "policy", *obj) - return nil, nil, err + return nil, nil, nil, err } nsxSecurityPolicy.Scope = []string{policyGroupPath} @@ -69,10 +73,10 @@ func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.Security for ruleIdx, rule := range obj.Spec.Rules { // A rule containing named port may expand to multiple rules if the name maps to multiple port numbers. - expandRules, ruleGroups, err := service.buildRuleAndGroups(obj, &rule, ruleIdx) + expandRules, ruleGroups, shares, err := service.buildRuleAndGroups(obj, &rule, ruleIdx) if err != nil { log.Error(err, "failed to build rule and groups", "rule", rule, "ruleIndex", ruleIdx) - return nil, nil, err + return nil, nil, nil, err } for _, nsxRule := range expandRules { if nsxRule != nil { @@ -87,11 +91,17 @@ func (service *SecurityPolicyService) buildSecurityPolicy(obj *v1alpha1.Security } } } + for _, share := range shares { + if share != nil { + projectShares = append(projectShares, *share) + } + } + } nsxSecurityPolicy.Rules = nsxRules nsxSecurityPolicy.Tags = service.buildBasicTags(obj) log.V(1).Info("built nsxSecurityPolicy", "nsxSecurityPolicy", nsxSecurityPolicy, "nsxGroups", nsxGroups) - return nsxSecurityPolicy, &nsxGroups, nil + return nsxSecurityPolicy, &nsxGroups, &projectShares, nil } func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPolicy) (*model.Group, string, error) { @@ -109,7 +119,7 @@ func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPol return nil, "ANY", nil } - targetGroupCount, targetGroupTotalExprCount := 0, 0 + targetGroupCriteriaCount, targetGroupTotalExprCount := 0, 0 criteriaCount, totalExprCount := 0, 0 var err error = nil errorMsg := "" @@ -121,19 +131,19 @@ func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPol i, ) if err == nil { - targetGroupCount += criteriaCount + targetGroupCriteriaCount += criteriaCount targetGroupTotalExprCount += totalExprCount } else { return nil, "", err } } - log.V(2).Info("build policy target group criteria", "total criteria", - targetGroupCount, "total expressions of criteria", targetGroupTotalExprCount) + log.V(2).Info("build policy target group criteria", + "total criteria", targetGroupCriteriaCount, "total expressions of criteria", targetGroupTotalExprCount) - if targetGroupCount > MaxCriteria { + if targetGroupCriteriaCount > MaxCriteria { errorMsg = fmt.Sprintf( "total counts of policy target group criteria %d exceed NSX limit of %d", - targetGroupCount, + targetGroupCriteriaCount, MaxCriteria, ) } else if targetGroupTotalExprCount > MaxTotalCriteriaExpressions { @@ -146,8 +156,13 @@ func (service *SecurityPolicyService) buildPolicyGroup(obj *v1alpha1.SecurityPol return nil, "", err } - policyGroupPath := service.buildPolicyGroupPath(obj) - return &policyGroup, policyGroupPath, nil + policyAppliedGroupPath, err := service.buildAppliedGroupPath(obj, "") + if err != nil { + return nil, "", err + } + + log.V(1).Info("built policy target group", "policyGroup", policyGroup) + return &policyGroup, policyAppliedGroupPath, nil } func (service *SecurityPolicyService) buildTargetTags(obj *v1alpha1.SecurityPolicy, targets *[]v1alpha1.SecurityPolicyTarget, idx int) []model.Tag { @@ -161,16 +176,30 @@ func (service *SecurityPolicyService) buildTargetTags(obj *v1alpha1.SecurityPoli targetTags := []model.Tag{ { Scope: String(common.TagScopeGroupType), - Tag: String("scope"), + Tag: String(common.TagValueGroupScope), }, { Scope: String(common.TagScopeSelectorHash), Tag: String(util.Sha1(string(serializedBytes))), }, } + for _, tag := range basicTags { targetTags = append(targetTags, tag) } + + // In non-VPC network, there is no need to add is_project_shared tag for target groups + if isVpcEnabled(service) { + // the target group for policy or rule is always not group shared + // because target group doesn't have nameSpace selector + targetTags = append(targetTags, + model.Tag{ + Scope: String(common.TagScopeProjectGroupShared), + Tag: String("false"), + }, + ) + } + if idx != -1 { // the appliedTo group belongs to a rule, so it needs a tag including the rule id targetTags = append(targetTags, @@ -183,8 +212,13 @@ func (service *SecurityPolicyService) buildTargetTags(obj *v1alpha1.SecurityPoli return targetTags } +// Todo, use the uitl basic func to generate basic tags func (service *SecurityPolicyService) buildBasicTags(obj *v1alpha1.SecurityPolicy) []model.Tag { tags := []model.Tag{ + { + Scope: String(common.TagScopeVersion), + Tag: String(strings.Join(common.TagValueVersion, ".")), + }, { Scope: String(common.TagScopeCluster), Tag: String(getCluster(service)), @@ -193,7 +227,10 @@ func (service *SecurityPolicyService) buildBasicTags(obj *v1alpha1.SecurityPolic Scope: String(common.TagScopeNamespace), Tag: String(obj.ObjectMeta.Namespace), }, - // TODO: get namespace uid + { + Scope: String(common.TagScopeNamespaceUID), + Tag: String(string(service.getNamespaceUID(obj.ObjectMeta.Namespace))), + }, { Scope: String(common.TagScopeSecurityPolicyCRName), Tag: String(obj.ObjectMeta.Name), @@ -313,15 +350,33 @@ func (service *SecurityPolicyService) buildPolicyGroupID(obj *v1alpha1.SecurityP return fmt.Sprintf("sp_%s_scope", obj.UID) } -func (service *SecurityPolicyService) buildPolicyGroupPath(obj *v1alpha1.SecurityPolicy) string { - return fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), service.buildPolicyGroupID(obj)) +// build appliedTo group path for both policy and rule levels. +func (service *SecurityPolicyService) buildAppliedGroupPath(obj *v1alpha1.SecurityPolicy, groupID string) (string, error) { + if groupID == "" { + groupID = service.buildPolicyGroupID(obj) + } + + if isVpcEnabled(service) { + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return "", err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + vpcId := (*vpcInfo).VPCID + return fmt.Sprintf("/orgs/%s/projects/%s/vpcs/%s/groups/%s", orgId, projectId, vpcId, groupID), nil + } + + return fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), groupID), nil } -func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int) ([]*model.Rule, []*model.Group, error) { +func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int) ([]*model.Rule, []*model.Group, []*ProjectShare, error) { var ruleGroups []*model.Group + var projectShares []*ProjectShare var nsxRuleAppliedGroup *model.Group var nsxRuleSrcGroup *model.Group var nsxRuleDstGroup *model.Group + var nsxProjectShare *ProjectShare var nsxRuleAppliedGroupPath string var nsxRuleDstGroupPath string var nsxRuleSrcGroupPath string @@ -329,14 +384,14 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP ruleDirection, err := getRuleDirection(rule) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Since a named port may map to multiple port numbers, then it would return multiple rules. // We use the destination port number of service entry to group the rules. ipSetGroups, nsxRules, err := service.expandRule(obj, rule, ruleIdx) if err != nil { - return nil, nil, err + return nil, nil, nil, err } for _, g := range ipSetGroups { ruleGroups = append(ruleGroups, g) @@ -344,22 +399,37 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP for _, nsxRule := range nsxRules { if ruleDirection == "IN" { - nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, err = service.buildRuleInGroup( + nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRuleInGroup( obj, rule, nsxRule, ruleIdx, ) if err != nil { - return nil, nil, err + return nil, nil, nil, err + } + if nsxRuleSrcGroup != nil { + ruleGroups = append(ruleGroups, nsxRuleSrcGroup) + } + if nsxProjectShare != nil { + projectShares = append(projectShares, nsxProjectShare) } - ruleGroups = append(ruleGroups, nsxRuleSrcGroup) } else if ruleDirection == "OUT" { - nsxRuleDstGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, err = service.buildRuleOutGroup(obj, rule, nsxRule, ruleIdx) + nsxRuleDstGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRuleOutGroup( + obj, + rule, + nsxRule, + ruleIdx, + ) if err != nil { - return nil, nil, err + return nil, nil, nil, err + } + if nsxRuleDstGroup != nil { + ruleGroups = append(ruleGroups, nsxRuleDstGroup) + } + if nsxProjectShare != nil { + projectShares = append(projectShares, nsxProjectShare) } - ruleGroups = append(ruleGroups, nsxRuleDstGroup) } nsxRule.SourceGroups = []string{nsxRuleSrcGroupPath} nsxRule.DestinationGroups = []string{nsxRuleDstGroupPath} @@ -372,16 +442,16 @@ func (service *SecurityPolicyService) buildRuleAndGroups(obj *v1alpha1.SecurityP nsxRuleDstGroupPath, ) if err != nil { - return nil, nil, err + return nil, nil, nil, err } ruleGroups = append(ruleGroups, nsxRuleAppliedGroup) nsxRule.Scope = []string{nsxRuleAppliedGroupPath} - log.V(2).Info("built rule and groups", "nsxRuleAppliedGroup", nsxRuleAppliedGroup, + log.V(1).Info("built rule and groups", "nsxRuleAppliedGroup", nsxRuleAppliedGroup, "~", nsxRuleSrcGroup, "nsxRuleDstGroup", nsxRuleDstGroup, "action", *nsxRule.Action, "direction", *nsxRule.Direction) } - return nsxRules, ruleGroups, nil + return nsxRules, ruleGroups, projectShares, nil } func (service *SecurityPolicyService) buildRuleServiceEntries(port v1alpha1.SecurityPolicyPort, portAddress nsxutil.PortAddress) *data.StructValue { @@ -411,7 +481,7 @@ func (service *SecurityPolicyService) buildRuleServiceEntries(port v1alpha1.Secu "overridden": data.NewBooleanValue(false), }, ) - log.V(2).Info("built service entry", "serviceEntry", serviceEntry) + log.V(1).Info("built service entry", "serviceEntry", serviceEntry) return serviceEntry } @@ -438,15 +508,16 @@ func (service *SecurityPolicyService) buildRuleAppliedToGroup(obj *v1alpha1.Secu return nsxRuleAppliedGroup, nsxRuleAppliedGroupPath, nil } -func (service *SecurityPolicyService) buildRuleInGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int) (*model.Group, string, string, error) { +func (service *SecurityPolicyService) buildRuleInGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int) (*model.Group, string, string, *ProjectShare, error) { var nsxRuleSrcGroup *model.Group + var nsxProjectShare *ProjectShare var nsxRuleSrcGroupPath string var nsxRuleDstGroupPath string var err error if len(rule.Sources) > 0 { - nsxRuleSrcGroup, nsxRuleSrcGroupPath, err = service.buildRuleSrcGroup(obj, rule, ruleIdx) + nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxProjectShare, err = service.buildRulePeerGroup(obj, rule, ruleIdx, true) if err != nil { - return nil, "", "", err + return nil, "", "", nil, err } } else { nsxRuleSrcGroupPath = "ANY" @@ -457,11 +528,12 @@ func (service *SecurityPolicyService) buildRuleInGroup(obj *v1alpha1.SecurityPol } else { nsxRuleDstGroupPath = "ANY" } - return nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nil + return nsxRuleSrcGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, nil } -func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int) (*model.Group, string, string, error) { +func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, nsxRule *model.Rule, ruleIdx int) (*model.Group, string, string, *ProjectShare, error) { var nsxRuleDstGroup *model.Group + var nsxProjectShare *ProjectShare var nsxRuleSrcGroupPath string var nsxRuleDstGroupPath string var err error @@ -469,16 +541,16 @@ func (service *SecurityPolicyService) buildRuleOutGroup(obj *v1alpha1.SecurityPo nsxRuleDstGroupPath = nsxRule.DestinationGroups[0] } else { if len(rule.Destinations) > 0 { - nsxRuleDstGroup, nsxRuleDstGroupPath, err = service.buildRuleDstGroup(obj, rule, ruleIdx) + nsxRuleDstGroup, nsxRuleDstGroupPath, nsxProjectShare, err = service.buildRulePeerGroup(obj, rule, ruleIdx, false) if err != nil { - return nil, "", "", err + return nil, "", "", nil, err } } else { nsxRuleDstGroupPath = "ANY" } } nsxRuleSrcGroupPath = "ANY" - return nsxRuleDstGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nil + return nsxRuleDstGroup, nsxRuleSrcGroupPath, nsxRuleDstGroupPath, nsxProjectShare, nil } func (service *SecurityPolicyService) buildRuleID(obj *v1alpha1.SecurityPolicy, idx int) string { @@ -495,6 +567,7 @@ func (service *SecurityPolicyService) buildRuleName(obj *v1alpha1.SecurityPolicy func (service *SecurityPolicyService) buildRuleAppliedGroupByPolicy(obj *v1alpha1.SecurityPolicy, nsxRuleSrcGroupPath string, nsxRuleDstGroupPath string) (string, error) { var nsxRuleAppliedGroupPath string + var err error if len(obj.Spec.AppliedTo) == 0 { return "", errors.New("appliedTo needs to be set in either spec or rules") } @@ -502,7 +575,10 @@ func (service *SecurityPolicyService) buildRuleAppliedGroupByPolicy(obj *v1alpha // NSX-T manager will report error if all the rule's scope/src/dst are "ANY". // So if the rule's scope is empty while policy's not, the rule's scope also // will be set to the policy's scope to avoid this case. - nsxRuleAppliedGroupPath = service.buildPolicyGroupPath(obj) + nsxRuleAppliedGroupPath, err = service.buildAppliedGroupPath(obj, "") + if err != nil { + return "", err + } } else { nsxRuleAppliedGroupPath = "ANY" } @@ -519,16 +595,18 @@ func (service *SecurityPolicyService) buildRuleAppliedGroupByRule(obj *v1alpha1. ruleAppliedGroupName = fmt.Sprintf("%s-%d-scope", obj.ObjectMeta.Name, idx) } targetTags := service.buildTargetTags(obj, &appliedTo, idx) - ruleAppliedGroupPath := fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), ruleAppliedGroupID) + ruleAppliedGroupPath, err := service.buildAppliedGroupPath(obj, ruleAppliedGroupID) + if err != nil { + return nil, "", err + } ruleAppliedGroup := model.Group{ Id: &ruleAppliedGroupID, DisplayName: &ruleAppliedGroupName, Tags: targetTags, } - ruleGroupCount, ruleGroupTotalExprCount := 0, 0 + ruleGroupCriteriaCount, ruleGroupTotalExprCount := 0, 0 criteriaCount, totalExprCount := 0, 0 - var err error = nil errorMsg := "" for i, target := range appliedTo { criteriaCount, totalExprCount, err = service.updateTargetExpressions( @@ -538,18 +616,19 @@ func (service *SecurityPolicyService) buildRuleAppliedGroupByRule(obj *v1alpha1. i, ) if err == nil { - ruleGroupCount += criteriaCount + ruleGroupCriteriaCount += criteriaCount ruleGroupTotalExprCount += totalExprCount } else { return nil, "", err } } - log.V(2).Info("build rule applied group criteria", "total criteria", ruleGroupCount, "total expressions of criteria", ruleGroupTotalExprCount) + log.V(2).Info("build rule applied group criteria", "total criteria", + ruleGroupCriteriaCount, "total expressions of criteria", ruleGroupTotalExprCount) - if ruleGroupCount > MaxCriteria { + if ruleGroupCriteriaCount > MaxCriteria { errorMsg = fmt.Sprintf( "total counts of rule applied group criteria %d exceed NSX limit of %d", - ruleGroupCount, + ruleGroupCriteriaCount, MaxCriteria, ) } else if ruleGroupTotalExprCount > MaxTotalCriteriaExpressions { @@ -564,114 +643,149 @@ func (service *SecurityPolicyService) buildRuleAppliedGroupByRule(obj *v1alpha1. return &ruleAppliedGroup, ruleAppliedGroupPath, nil } -func (service *SecurityPolicyService) buildRuleSrcGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, idx int) (*model.Group, string, error) { - var ruleSrcGroupName string - sources := rule.Sources - ruleSrcGroupID := fmt.Sprintf("sp_%s_%d_src", obj.UID, idx) - if len(rule.Name) > 0 { - ruleSrcGroupName = fmt.Sprintf("%s-src", rule.Name) - } else { - ruleSrcGroupName = fmt.Sprintf("%s-%d-src", obj.ObjectMeta.Name, idx) +func (service *SecurityPolicyService) buildRulePeerGroupPath(obj *v1alpha1.SecurityPolicy, groupID string, groupShared bool) (string, error) { + if isVpcEnabled(service) { + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return "", err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + vpcId := (*vpcInfo).VPCID + + if groupShared { + return fmt.Sprintf("/orgs/%s/projects/%s/infra/domains/%s/groups/%s", orgId, projectId, getVpcProjectDomain(), groupID), nil + } + return fmt.Sprintf("/orgs/%s/projects/%s/vpcs/%s/groups/%s", orgId, projectId, vpcId, groupID), nil } - ruleSrcGroupPath := fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), ruleSrcGroupID) - peerTags := service.BuildPeerTags(obj, &sources, idx) - ruleSrcGroup := model.Group{ - Id: &ruleSrcGroupID, - DisplayName: &ruleSrcGroupName, - Tags: peerTags, + + return fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), groupID), nil +} + +func (service *SecurityPolicyService) buildRulePeerGroupID(obj *v1alpha1.SecurityPolicy, idx int, isSource bool) string { + if isSource == true { + return fmt.Sprintf("sp_%s_%d_src", obj.UID, idx) + } else { + return fmt.Sprintf("sp_%s_%d_dst", obj.UID, idx) } +} - ruleSrcGroupCount, ruleSrcGroupTotalExprCount := 0, 0 - criteriaCount, totalExprCount := 0, 0 - var err error = nil - errorMsg := "" - for i, peer := range sources { - criteriaCount, totalExprCount, err = service.updatePeerExpressions( - obj, - &peer, - &ruleSrcGroup, - i, - ) - if err == nil { - ruleSrcGroupCount += criteriaCount - ruleSrcGroupTotalExprCount += totalExprCount +func (service *SecurityPolicyService) buildRulePeerGroupName(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, idx int, isSource bool) string { + if isSource == true { + if len(rule.Name) > 0 { + return fmt.Sprintf("%s-src", rule.Name) } else { - return nil, "", err + return fmt.Sprintf("%s-%d-src", obj.ObjectMeta.Name, idx) + } + } else { + if len(rule.Name) > 0 { + return fmt.Sprintf("%s-dst", rule.Name) + } else { + return fmt.Sprintf("%s-%d-dst", obj.ObjectMeta.Name, idx) } } - log.V(2).Info("build rule source group criteria", "total criteria", ruleSrcGroupCount, "total expressions of criteria", ruleSrcGroupTotalExprCount) +} - if ruleSrcGroupCount > MaxCriteria { - errorMsg = fmt.Sprintf( - "total counts of rule source group criteria %d exceed NSX limit of %d", - ruleSrcGroupCount, - MaxCriteria, - ) - } else if ruleSrcGroupTotalExprCount > MaxTotalCriteriaExpressions { - errorMsg = fmt.Sprintf("total expression counts in source group criteria %d exceed NSX limit of %d", ruleSrcGroupTotalExprCount, MaxTotalCriteriaExpressions) +func (service *SecurityPolicyService) buildRulePeerGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, idx int, isSource bool) (*model.Group, string, *ProjectShare, error) { + var rulePeers []v1alpha1.SecurityPolicyPeer + var ruleDirection string + rulePeerGroupID := service.buildRulePeerGroupID(obj, idx, isSource) + rulePeerGroupName := service.buildRulePeerGroupName(obj, rule, idx, isSource) + if isSource == true { + rulePeers = rule.Sources + ruleDirection = "source" + } else { + rulePeers = rule.Destinations + ruleDirection = "destination" } - if len(errorMsg) != 0 { - err = errors.New(errorMsg) - return nil, "", err + groupShared := false + for _, peer := range rulePeers { + if peer.NamespaceSelector != nil { + groupShared = true + break + } } - return &ruleSrcGroup, ruleSrcGroupPath, err -} - -// TODO: merge buildRuleSrcGroup and buildRuleDstGroup -func (service *SecurityPolicyService) buildRuleDstGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, idx int) (*model.Group, string, error) { - var ruleDstGroupName string - destinations := rule.Destinations - ruleDstGroupID := fmt.Sprintf("sp_%s_%d_dst", obj.UID, idx) - if len(rule.Name) > 0 { - ruleDstGroupName = fmt.Sprintf("%s-dst", rule.Name) - } else { - ruleDstGroupName = fmt.Sprintf("%s-%d-dst", obj.ObjectMeta.Name, idx) + rulePeerGroupPath, err := service.buildRulePeerGroupPath(obj, rulePeerGroupID, groupShared) + if err != nil { + return nil, "", nil, err } - ruleDstGroupPath := fmt.Sprintf("/infra/domains/%s/groups/%s", getDomain(service), ruleDstGroupID) - peerTags := service.BuildPeerTags(obj, &destinations, idx) - ruleDstGroup := model.Group{ - Id: &ruleDstGroupID, - DisplayName: &ruleDstGroupName, + + peerTags := service.BuildPeerTags(obj, &rulePeers, idx, isSource, groupShared) + rulePeerGroup := model.Group{ + Id: &rulePeerGroupID, + DisplayName: &rulePeerGroupName, Tags: peerTags, } - ruleDstGroupCount, ruleDstGroupTotalExprCount := 0, 0 + rulePeerGroupCriteriaCount, rulePeerGroupTotalExprCount := 0, 0 criteriaCount, totalExprCount := 0, 0 - var err error = nil errorMsg := "" - for i, peer := range destinations { + for i, peer := range rulePeers { criteriaCount, totalExprCount, err = service.updatePeerExpressions( obj, &peer, - &ruleDstGroup, + &rulePeerGroup, i, + groupShared, ) if err == nil { - ruleDstGroupCount += criteriaCount - ruleDstGroupTotalExprCount += totalExprCount + rulePeerGroupCriteriaCount += criteriaCount + rulePeerGroupTotalExprCount += totalExprCount } else { - return nil, "", err + return nil, "", nil, err } } - log.V(2).Info("build rule destination group criteria", "total criteria", ruleDstGroupCount, "total expressions of criteria", ruleDstGroupTotalExprCount) + log.V(2).Info(fmt.Sprintf("build rule %s group criteria", ruleDirection), + "total criteria", rulePeerGroupCriteriaCount, "total expressions of criteria", rulePeerGroupTotalExprCount) - if ruleDstGroupCount > MaxCriteria { + if rulePeerGroupCriteriaCount > MaxCriteria { errorMsg = fmt.Sprintf( - "total counts of rule destination group criteria %d exceed NSX limit of %d", - ruleDstGroupCount, + "total counts of rule %s group criteria %d exceed NSX limit of %d", + ruleDirection, + rulePeerGroupCriteriaCount, MaxCriteria, ) - } else if ruleDstGroupTotalExprCount > MaxTotalCriteriaExpressions { - errorMsg = fmt.Sprintf("total expression counts in rule destination group criteria %d exceed NSX limit of %d", ruleDstGroupTotalExprCount, MaxTotalCriteriaExpressions) + } else if rulePeerGroupTotalExprCount > MaxTotalCriteriaExpressions { + errorMsg = fmt.Sprintf("total expression counts in %s group criteria %d exceed NSX limit of %d", + ruleDirection, + rulePeerGroupTotalExprCount, + MaxTotalCriteriaExpressions, + ) } if len(errorMsg) != 0 { err = errors.New(errorMsg) - return nil, "", err + return nil, "", nil, err + } + + if isVpcEnabled(service) && (groupShared == true) { + var projectShare ProjectShare + projectShare.shareGroup = &rulePeerGroup + + var sharedNamespace []string + // Share group with the VPC in which SecurityPolicy rule is put + sharedNamespace = append(sharedNamespace, obj.ObjectMeta.Namespace) + + sharedWith, err := service.buildSharedWith(&sharedNamespace) + if err != nil { + log.Error(err, "failed to build SharedWith path", "rule group Name", rulePeerGroupName) + return nil, "", nil, err + } + // Build a nsx share resource in project level + share, err := service.buildProjectShare(obj, &rulePeerGroup, []string{rulePeerGroupPath}, *sharedWith) + if err != nil { + log.Error(err, "failed to build project share", "rule group Name", rulePeerGroupName) + return nil, "", nil, err + } + + projectShare.share = share + log.V(1).Info("built nsx project share resource", "share", share) + return nil, rulePeerGroupPath, &projectShare, err } - return &ruleDstGroup, ruleDstGroupPath, err + + return &rulePeerGroup, rulePeerGroupPath, nil, err } // Build rule basic info, ruleIdx is the index of the rules of security policy, @@ -700,7 +814,7 @@ func (service *SecurityPolicyService) buildRuleBasicInfo(obj *v1alpha1.SecurityP return &nsxRule, nil } -func (service *SecurityPolicyService) BuildPeerTags(obj *v1alpha1.SecurityPolicy, peers *[]v1alpha1.SecurityPolicyPeer, idx int) []model.Tag { +func (service *SecurityPolicyService) BuildPeerTags(obj *v1alpha1.SecurityPolicy, peers *[]v1alpha1.SecurityPolicyPeer, idx int, isSource, groupShared bool) []model.Tag { basicTags := service.buildBasicTags(obj) // TODO: abstract sort func for both peers and targets sort.Slice(*peers, func(i, j int) bool { @@ -709,10 +823,19 @@ func (service *SecurityPolicyService) BuildPeerTags(obj *v1alpha1.SecurityPolicy return string(k1) < string(k2) }) serializedBytes, _ := json.Marshal(*peers) + + groupTypeTag := String(common.TagValueGroupDst) + if isSource == true { + groupTypeTag = String(common.TagValueGroupSrc) + } + groupSharedTag := String("false") + if groupShared == true { + groupSharedTag = String("true") + } peerTags := []model.Tag{ { Scope: String(common.TagScopeGroupType), - Tag: String("scope"), + Tag: groupTypeTag, }, { Scope: String(common.TagScopeRuleID), @@ -726,44 +849,61 @@ func (service *SecurityPolicyService) BuildPeerTags(obj *v1alpha1.SecurityPolicy for _, tag := range basicTags { peerTags = append(peerTags, tag) } + + // In non-VPC network, there is no need to add is_project_shared tag for rule peer groups + if isVpcEnabled(service) { + peerTags = append(peerTags, + model.Tag{ + Scope: String(common.TagScopeProjectGroupShared), + Tag: groupSharedTag, + }, + ) + } return peerTags } func (service *SecurityPolicyService) updateTargetExpressions(obj *v1alpha1.SecurityPolicy, target *v1alpha1.SecurityPolicyTarget, group *model.Group, idx int) (int, int, error) { var err error = nil var tagValueExpression *data.StructValue = nil - memberType := "SegmentPort" var matchLabels map[string]string var matchExpressions *[]v1.LabelSelectorRequirement = nil var mergedMatchExpressions *[]v1.LabelSelectorRequirement = nil opInValueCount, totalCriteriaCount, totalExprCount := 0, 0, 0 matchLabelsCount, matchExpressionsCount := 0, 0 + memberType, clusterMemberType := "SegmentPort", "Segment" + if isVpcEnabled(service) { + memberType, clusterMemberType = "VpcSubnetPort", "VpcSubnetPort" + } + if target.PodSelector != nil && target.VMSelector != nil { errorMsg := "PodSelector and VMSelector are not allowed to set in one group" err = errors.New(errorMsg) return 0, 0, err } - log.V(2).Info("build target expressions", "index", idx) + log.V(2).Info("update target expressions", "index", idx) service.appendOperatorIfNeeded(&group.Expression, "OR") expressions := service.buildGroupExpression(&group.Expression) - // Setting cluster member type to "Segment" for PodSelector and VMSelector ensure the criteria is mixed - // because the following conditions must have condition whose memberType=SegmentPort + // In non-VPC network, setting cluster memberType to Segment for PodSelector and VMSelector ensures the criteria is mixed, + // Because the following conditions must have condition whose memberType is SegmentPort. + // In VPC network, setting cluster memberType VpcSubnetPort because VPC level group doesn't support mixed criteria. + // Also, following conditions must have condition whose memberType is VpcSubnetPort. + // Segment and SegmentPort are not supported in VPC level group. + // Target group must be put under VPC level group path. clusterExpression := service.buildExpression( - "Condition", "Segment", - fmt.Sprintf("%s|%s", common.TagScopeNCPCluster, getCluster(service)), + "Condition", clusterMemberType, + fmt.Sprintf("%s|%s", getScopeCluserTag(service), getCluster(service)), "Tag", "EQUALS", "EQUALS", ) expressions.Add(clusterExpression) if target.PodSelector != nil { service.addOperatorIfNeeded(expressions, "AND") - // TODO: consider to use project_uid instead of project nsExpression := service.buildExpression( "Condition", memberType, - fmt.Sprintf("%s|%s", common.TagScopeNCPProject, obj.ObjectMeta.Namespace), + fmt.Sprintf("%s|%s", getScopeNamespaceUIDTag(service, false), string(service.getNamespaceUID(obj.ObjectMeta.Namespace))), "Tag", "EQUALS", "EQUALS", ) expressions.Add(nsExpression) @@ -776,7 +916,7 @@ func (service *SecurityPolicyService) updateTargetExpressions(obj *v1alpha1.Secu service.addOperatorIfNeeded(expressions, "AND") nsExpression := service.buildExpression( "Condition", memberType, - fmt.Sprintf("%s|%s", common.TagScopeNCPVIFProject, obj.ObjectMeta.Namespace), + fmt.Sprintf("%s|%s", getScopeNamespaceUIDTag(service, true), string(service.getNamespaceUID(obj.ObjectMeta.Namespace))), "Tag", "EQUALS", "EQUALS", ) expressions.Add(nsExpression) @@ -789,7 +929,7 @@ func (service *SecurityPolicyService) updateTargetExpressions(obj *v1alpha1.Secu service.updateExpressionsMatchLabels(matchLabels, memberType, expressions) matchLabelsCount = len(matchLabels) // PodSelector or VMSelector has two more built-in labels - matchLabelsCount += ClusterTagCount + ProjectTagCount + matchLabelsCount += ClusterTagCount + NameSpaceTagCount if matchExpressions != nil { mergedMatchExpressions = service.mergeSelectorMatchExpression(*matchExpressions) @@ -806,13 +946,23 @@ func (service *SecurityPolicyService) updateTargetExpressions(obj *v1alpha1.Secu } } - // Since cluster is set as default "Segment" memberType, So the final produced group criteria is always treated as a mixed criteria - totalCriteriaCount, totalExprCount, err = service.validateSelectorExpressions( - matchLabelsCount, - matchExpressionsCount, - opInValueCount, - true, - ) + if isVpcEnabled(service) { + // In VPC level, it doesn't support mixed expression criteria + totalCriteriaCount, totalExprCount, err = service.validateSelectorExpressions( + matchLabelsCount, + matchExpressionsCount, + opInValueCount, + false, + ) + } else { + // Since cluster memberType is set as default Segment, So the final produced group criteria is always treated as a mixed criteria + totalCriteriaCount, totalExprCount, err = service.validateSelectorExpressions( + matchLabelsCount, + matchExpressionsCount, + opInValueCount, + true, + ) + } if err != nil { return 0, 0, err } @@ -1142,17 +1292,19 @@ func (service *SecurityPolicyService) updateMixedExpressionsMatchExpression(nsMa return err } -func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.SecurityPolicy, peer *v1alpha1.SecurityPolicyPeer, group *model.Group, idx int) (int, int, error) { +func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.SecurityPolicy, peer *v1alpha1.SecurityPolicyPeer, group *model.Group, + idx int, groupShared bool, +) (int, int, error) { var err error = nil errorMsg := "" var tagValueExpression *data.StructValue = nil - var memberType string var matchLabels map[string]string var matchExpressions *[]v1.LabelSelectorRequirement = nil var mergedMatchExpressions *[]v1.LabelSelectorRequirement = nil opInValueCount, totalCriteriaCount, totalExprCount := 0, 0, 0 matchLabelsCount, matchExpressionsCount := 0, 0 mixedNsSelector := false + isVpcEnable := isVpcEnabled(service) if len(peer.IPBlocks) > 0 { addresses := data.NewListValue() @@ -1188,30 +1340,42 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi service.appendOperatorIfNeeded(&group.Expression, "OR") expressions := service.buildGroupExpression(&group.Expression) - // Setting cluster member type to "Segment" for PodSelector and VMSelector ensure the criteria is mixed - // because the following conditions must have condition whose memberType=SegmentPort + // In non-VPC network, setting cluster memberType to Segment for PodSelector and VMSelector ensures the criteria is mixed, + // Because the following conditions must have condition whose memberType is SegmentPort clusterMemberType := "Segment" - // Setting cluster member type to "SegmentPort" for NamespaceSelector ensure the criteria is mixed - // because the following conditions must have condition whose memberType=Segment when NamespaceSelector isn't empty + // Setting cluster memberType to SegmentPort for NamespaceSelector only case ensures the criteria is mixed, + // Because the following conditions must have condition whose memberType is Segment when NamespaceSelector isn't empty if peer.PodSelector == nil && peer.VMSelector == nil && peer.NamespaceSelector != nil && peer.NamespaceSelector.Size() > 0 { clusterMemberType = "SegmentPort" } + memberType := "SegmentPort" + // In VPC network, cluster memberType must be set as VpcSubnetPort when no NamespaceSelector. + // Moreover, VPC level group doesn't support mixed criteria but project level group can support mixed criteria. + // MemberType must be set as VpcSubnetPort for the VPC level group. + // If NamespaceSelector is specified, peer group must be put under project level for sharing with VPC. + // VpcSubnet and VpcSubnetPort types are not supported in project level group. + // Project level group can support SegmentPort and Segment type. + // If groupShared is True, it means there are NamespaceSelectors in the rule groups, so we can use mixed criteria. + if isVpcEnable && !groupShared { + clusterMemberType = "VpcSubnetPort" + memberType = "VpcSubnetPort" + } + clusterExpression := service.buildExpression( "Condition", clusterMemberType, - fmt.Sprintf("%s|%s", common.TagScopeNCPCluster, getCluster(service)), + fmt.Sprintf("%s|%s", getScopeCluserTag(service), getCluster(service)), "Tag", "EQUALS", "EQUALS", ) expressions.Add(clusterExpression) if peer.PodSelector != nil { - memberType = "SegmentPort" service.addOperatorIfNeeded(expressions, "AND") podExpression := service.buildExpression( "Condition", memberType, - fmt.Sprintf("%s|", common.TagScopeNCPPod), + fmt.Sprintf("%s|", getScopePodTag(service)), "Tag", "EQUALS", "EQUALS", @@ -1220,7 +1384,7 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi if peer.NamespaceSelector == nil { podExpression = service.buildExpression( "Condition", memberType, - fmt.Sprintf("%s|%s", common.TagScopeNCPProject, obj.ObjectMeta.Namespace), + fmt.Sprintf("%s|%s", getScopeNamespaceUIDTag(service, false), string(service.getNamespaceUID(obj.ObjectMeta.Namespace))), "Tag", "EQUALS", "EQUALS") mixedNsSelector = false } else { @@ -1233,15 +1397,14 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi matchExpressions = &peer.PodSelector.MatchExpressions matchLabelsCount = len(matchLabels) // PodSelector has two more built-in labels - matchLabelsCount += ClusterTagCount + ProjectTagCount + matchLabelsCount += ClusterTagCount + NameSpaceTagCount } if peer.VMSelector != nil { - memberType = "SegmentPort" service.addOperatorIfNeeded(expressions, "AND") vmExpression := service.buildExpression( "Condition", memberType, - fmt.Sprintf("%s|", common.TagScopeNCPVNETInterface), + fmt.Sprintf("%s|", getScopeVMInterfaceTag(service)), "Tag", "EQUALS", "EQUALS", @@ -1250,7 +1413,7 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi if peer.NamespaceSelector == nil { vmExpression = service.buildExpression( "Condition", memberType, - fmt.Sprintf("%s|%s", common.TagScopeNCPVIFProject, obj.ObjectMeta.Namespace), + fmt.Sprintf("%s|%s", getScopeNamespaceUIDTag(service, true), string(service.getNamespaceUID(obj.ObjectMeta.Namespace))), "Tag", "EQUALS", "EQUALS") mixedNsSelector = false } else { @@ -1263,7 +1426,7 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi matchExpressions = &peer.VMSelector.MatchExpressions matchLabelsCount = len(matchLabels) // VMSelector has two more built-in labels - matchLabelsCount += ClusterTagCount + ProjectTagCount + matchLabelsCount += ClusterTagCount + NameSpaceTagCount } if peer.NamespaceSelector != nil { if !mixedNsSelector { @@ -1276,7 +1439,7 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi service.addOperatorIfNeeded(expressions, "AND") clusterSegPortExpression := service.buildExpression( "Condition", "SegmentPort", - fmt.Sprintf("%s|%s", common.TagScopeNCPCluster, getCluster(service)), + fmt.Sprintf("%s|%s", getScopeCluserTag(service), getCluster(service)), "Tag", "EQUALS", "EQUALS", ) expressions.Add(clusterSegPortExpression) @@ -1372,13 +1535,23 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi } } - // Since cluster is set as "Segment" or "SegmentPort" memberType, So the final produced group criteria is always treated as a mixed criteria - totalCriteriaCount, totalExprCount, err = service.validateSelectorExpressions( - matchLabelsCount, - matchExpressionsCount, - opInValueCount, - true, - ) + if isVpcEnable && !groupShared { + // In VPC level, it doesn't support mixed expression criteria + totalCriteriaCount, totalExprCount, err = service.validateSelectorExpressions( + matchLabelsCount, + matchExpressionsCount, + opInValueCount, + false, + ) + } else { + // Since cluster memberType is set as "Segment" or "SegmentPort", So the final produced group criteria is always treated as a mixed criteria + totalCriteriaCount, totalExprCount, err = service.validateSelectorExpressions( + matchLabelsCount, + matchExpressionsCount, + opInValueCount, + true, + ) + } if err != nil { return 0, 0, err } @@ -1386,3 +1559,141 @@ func (service *SecurityPolicyService) updatePeerExpressions(obj *v1alpha1.Securi return totalCriteriaCount, totalExprCount, nil } + +func (service *SecurityPolicyService) buildShareID(nsxProjectName, groupID string) string { + nsxProjectShareId := fmt.Sprintf("share_%s_group_%s", nsxProjectName, groupID) + return nsxProjectShareId +} + +func (service *SecurityPolicyService) buildShareTags(obj *v1alpha1.SecurityPolicy, projectId string, group *model.Group) []model.Tag { + tags := []model.Tag{ + { + Scope: String(common.TagScopeVersion), + Tag: String(strings.Join(common.TagValueVersion, ".")), + }, + { + Scope: String(common.TagScopeCluster), + Tag: String(getCluster(service)), + }, + { + Scope: String(common.TagScopeNSXProjectID), + Tag: String(string(projectId)), + }, + { + Scope: String(common.TagScopeSecurityPolicyCRName), + Tag: String(obj.ObjectMeta.Name), + }, + { + Scope: String(common.TagScopeSecurityPolicyCRUID), + Tag: String(string(obj.UID)), + }, + { + Scope: String(common.TagScopeGoupID), + Tag: String(string(*group.Id)), + }, + } + return tags +} + +func (service *SecurityPolicyService) buildSharedResource(shareId string, sharedGroupPath []string) (*model.SharedResource, error) { + var resourceObjects []model.ResourceObject + resourceType := common.ResourceTypeSharedResource + includeChildren := false + + for _, groupPath := range sharedGroupPath { + resourceObject := model.ResourceObject{ + IncludeChildren: &includeChildren, + ResourcePath: &groupPath, + } + resourceObjects = append(resourceObjects, resourceObject) + } + sharedResource := model.SharedResource{ + Id: &shareId, + ResourceType: &resourceType, + ResourceObjects: resourceObjects, + } + + return &sharedResource, nil +} + +func (service *SecurityPolicyService) buildChildSharedResource(shareId string, sharedGroupPath []string) ([]*data.StructValue, error) { + resourceType := common.ResourceTypeChildSharedResource + sharedResource, err := service.buildSharedResource(shareId, sharedGroupPath) + if err != nil { + return nil, err + } + + childSharedResource := model.ChildSharedResource{ + ResourceType: resourceType, + SharedResource: sharedResource, + } + dataValue, errors := NewConverter().ConvertToVapi(childSharedResource, model.ChildSharedResourceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SecurityPolicyService) buildProjectShare(obj *v1alpha1.SecurityPolicy, group *model.Group, + sharedGroupPath, sharedWith []string, +) (*model.Share, error) { + resourceType := common.ResourceTypeShare + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return nil, err + } + projectId := (*vpcInfo).ProjectID + + projectShareId := service.buildShareID(projectId, string(*group.Id)) + projectShareTags := service.buildShareTags(obj, projectId, group) + projectShareName := fmt.Sprintf("share-%s-group-%s", projectId, *group.DisplayName) + + childSharedResource, err := service.buildChildSharedResource(projectShareId, sharedGroupPath) + if err != nil { + return nil, err + } + + projectShare := model.Share{ + Id: &projectShareId, + DisplayName: &projectShareName, + Tags: projectShareTags, + ResourceType: &resourceType, + SharedWith: sharedWith, + Children: childSharedResource, + } + + return &projectShare, nil +} + +func (service *SecurityPolicyService) buildSharedWith(sharedNamespace *[]string) (*[]string, error) { + var sharedWith []string + for _, ns := range *sharedNamespace { + log.V(1).Info("building sharedwith in nameSpace", "sharedNamespace", ns) + + vpcInfo, err := getVpcInfo(ns) + if err != nil { + return nil, err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + vpcId := (*vpcInfo).VPCID + + sharedWithPath := fmt.Sprintf("/orgs/%s/projects/%s/vpcs/%s", orgId, projectId, vpcId) + sharedWith = append(sharedWith, sharedWithPath) + } + + return &sharedWith, nil +} + +func (service *SecurityPolicyService) getNamespaceUID(ns string) (nsUid types.UID) { + namespace := &corev1.Namespace{} + namespacedName := types.NamespacedName{ + Name: ns, + } + if err := service.Client.Get(context.Background(), namespacedName, namespace); err != nil { + log.Error(err, "failed to get namespace UID", "namespace", ns) + return "" + } + namespace_uid := namespace.UID + return namespace_uid +} diff --git a/pkg/nsx/services/securitypolicy/builder_test.go b/pkg/nsx/services/securitypolicy/builder_test.go index 7f37995e5..7828228ae 100644 --- a/pkg/nsx/services/securitypolicy/builder_test.go +++ b/pkg/nsx/services/securitypolicy/builder_test.go @@ -1,12 +1,15 @@ package securitypolicy import ( + "reflect" "testing" + gomonkey "github.com/agiledragon/gomonkey/v2" "github.com/stretchr/testify/assert" "github.com/vmware/vsphere-automation-sdk-go/runtime/data" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" ) @@ -120,9 +123,17 @@ func TestBuildSecurityPolicy(t *testing.T) { }, }, } + + var s *SecurityPolicyService + patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(s), "getNamespaceUID", + func(s *SecurityPolicyService, ns string) types.UID { + return types.UID(tagValueNSUID) + }) + defer patches.Reset() + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - observedPolicy, _, _ := service.buildSecurityPolicy(tt.inputPolicy) + observedPolicy, _, _, _ := service.buildSecurityPolicy(tt.inputPolicy) assert.Equal(t, tt.expectedPolicy, observedPolicy) }) } @@ -144,6 +155,12 @@ func TestBuildPolicyGroup(t *testing.T) { expectedPolicyGroupPath: "/infra/domains/k8scl-one/groups/sp_uidA_scope", }, } + var s *SecurityPolicyService + patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(s), "getNamespaceUID", + func(s *SecurityPolicyService, ns string) types.UID { + return types.UID(tagValueNSUID) + }) + defer patches.Reset() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { observedGroup, observedGroupPath, _ := service.buildPolicyGroup(tt.inputPolicy) @@ -184,9 +201,13 @@ func TestBuildTargetTags(t *testing.T) { }, inputIndex: 0, expectedTags: []model.Tag{ + { + Scope: &tagScopeVersion, + Tag: &tagValueVersion, + }, { Scope: &tagScopeGroupType, - Tag: &tagValueScope, + Tag: &tagValueGroupScope, }, { Scope: &tagScopeSelectorHash, @@ -200,6 +221,10 @@ func TestBuildTargetTags(t *testing.T) { Scope: &tagScopeNamespace, Tag: &tagValueNS, }, + { + Scope: &tagScopeNamespaceUID, + Tag: &tagValueNSUID, + }, { Scope: &tagScopeSecurityPolicyCRName, Tag: &tagValuePolicyCRName, @@ -215,9 +240,15 @@ func TestBuildTargetTags(t *testing.T) { }, }, } + var s *SecurityPolicyService + patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(s), "getNamespaceUID", + func(s *SecurityPolicyService, ns string) types.UID { + return types.UID(tagValueNSUID) + }) + defer patches.Reset() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expectedTags, service.buildTargetTags(tt.inputPolicy, tt.inputTargets, tt.inputIndex)) + assert.ElementsMatch(t, tt.expectedTags, service.buildTargetTags(tt.inputPolicy, tt.inputTargets, tt.inputIndex)) }) } } @@ -234,9 +265,13 @@ func TestBuildPeerTags(t *testing.T) { inputPolicy: &spWithPodSelector, inputIndex: 0, expectedTags: []model.Tag{ + { + Scope: &tagScopeVersion, + Tag: &tagValueVersion, + }, { Scope: &tagScopeGroupType, - Tag: &tagValueScope, + Tag: &tagValueGroupSource, }, { Scope: &tagScopeRuleID, @@ -254,6 +289,10 @@ func TestBuildPeerTags(t *testing.T) { Scope: &tagScopeNamespace, Tag: &tagValueNS, }, + { + Scope: &tagScopeNamespaceUID, + Tag: &tagValueNSUID, + }, { Scope: &tagScopeSecurityPolicyCRName, Tag: &tagValuePolicyCRName, @@ -265,9 +304,15 @@ func TestBuildPeerTags(t *testing.T) { }, }, } + var s *SecurityPolicyService + patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(s), "getNamespaceUID", + func(s *SecurityPolicyService, ns string) types.UID { + return types.UID(tagValueNSUID) + }) + defer patches.Reset() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expectedTags, service.BuildPeerTags(tt.inputPolicy, &tt.inputPolicy.Spec.Rules[0].Sources, tt.inputIndex)) + assert.ElementsMatch(t, tt.expectedTags, service.BuildPeerTags(tt.inputPolicy, &tt.inputPolicy.Spec.Rules[0].Sources, tt.inputIndex, true, false)) }) } } diff --git a/pkg/nsx/services/securitypolicy/compare.go b/pkg/nsx/services/securitypolicy/compare.go index 09bb6e863..dac39cf94 100644 --- a/pkg/nsx/services/securitypolicy/compare.go +++ b/pkg/nsx/services/securitypolicy/compare.go @@ -11,6 +11,7 @@ type ( SecurityPolicy model.SecurityPolicy Rule model.Rule Group model.Group + Share model.Share ) type Comparable = common.Comparable @@ -27,6 +28,10 @@ func (rule *Rule) Key() string { return *rule.Id } +func (share *Share) Key() string { + return *share.Id +} + func (sp *SecurityPolicy) Value() data.DataValue { s := &SecurityPolicy{ Id: sp.Id, @@ -68,6 +73,18 @@ func (group *Group) Value() data.DataValue { return dataValue } +func (share *Share) Value() data.DataValue { + s := &Share{ + Id: share.Id, + DisplayName: share.DisplayName, + Tags: share.Tags, + SharedWith: share.SharedWith, + Children: share.Children, + } + dataValue, _ := ComparableToShare(s).GetDataValue__() + return dataValue +} + func SecurityPolicyToComparable(sp *model.SecurityPolicy) Comparable { return (*SecurityPolicy)(sp) } @@ -88,6 +105,18 @@ func GroupsToComparable(groups []model.Group) []Comparable { return res } +func ShareToComparable(share *model.Share) Comparable { + return (*Share)(share) +} + +func SharesToComparable(shares []model.Share) []Comparable { + res := make([]Comparable, 0, len(shares)) + for i := range shares { + res = append(res, (*Share)(&(shares[i]))) + } + return res +} + func ComparableToSecurityPolicy(sp Comparable) *model.SecurityPolicy { return (*model.SecurityPolicy)(sp.(*SecurityPolicy)) } @@ -115,3 +144,15 @@ func ComparableToGroups(groups []Comparable) []model.Group { func ComparableToGroup(group Comparable) *model.Group { return (*model.Group)(group.(*Group)) } + +func ComparableToShares(shares []Comparable) []model.Share { + res := make([]model.Share, 0, len(shares)) + for _, share := range shares { + res = append(res, (model.Share)(*(share.(*Share)))) + } + return res +} + +func ComparableToShare(share Comparable) *model.Share { + return (*model.Share)(share.(*Share)) +} diff --git a/pkg/nsx/services/securitypolicy/expand.go b/pkg/nsx/services/securitypolicy/expand.go index 3c1be1e06..be3c7b50c 100644 --- a/pkg/nsx/services/securitypolicy/expand.go +++ b/pkg/nsx/services/securitypolicy/expand.go @@ -21,7 +21,8 @@ import ( // When a rule contains named port, we should consider whether the rule should be expanded to // multiple rules if the port name maps to conflicted port numbers. func (service *SecurityPolicyService) expandRule(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, - ruleIdx int) ([]*model.Group, []*model.Rule, error) { + ruleIdx int, +) ([]*model.Group, []*model.Rule, error) { var nsxRules []*model.Rule var nsxGroups []*model.Group @@ -44,7 +45,8 @@ func (service *SecurityPolicyService) expandRule(obj *v1alpha1.SecurityPolicy, r } func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, - ruleIdx int, port v1alpha1.SecurityPolicyPort, portIdx int) ([]*model.Group, []*model.Rule, error) { + ruleIdx int, port v1alpha1.SecurityPolicyPort, portIdx int, +) ([]*model.Group, []*model.Rule, error) { var err error var startPort []nsxutil.PortAddress var nsxGroups []*model.Group @@ -69,7 +71,8 @@ func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPol ipSetGroup = group // Clear ip set group in nsx ipSetGroup.Expression = nil - err3 := service.createOrUpdateGroups([]model.Group{ipSetGroup}) + log.V(1).Info("clear ruleIPSetGroup", "ruleIPSetGroup", ipSetGroup) + err3 := service.createOrUpdateGroups(obj, []model.Group{ipSetGroup}) if err3 != nil { return nil, nil, err3 } @@ -91,7 +94,8 @@ func (service *SecurityPolicyService) expandRuleByPort(obj *v1alpha1.SecurityPol } func (service *SecurityPolicyService) expandRuleByService(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleIdx int, - port v1alpha1.SecurityPolicyPort, portIdx int, portAddress nsxutil.PortAddress, portAddressIdx int) ([]*model.Group, *model.Rule, error) { + port v1alpha1.SecurityPolicyPort, portIdx int, portAddress nsxutil.PortAddress, portAddressIdx int, +) ([]*model.Group, *model.Rule, error) { var nsxGroups []*model.Group nsxRule, err := service.buildRuleBasicInfo(obj, rule, ruleIdx, portIdx, portAddressIdx) @@ -107,23 +111,25 @@ func (service *SecurityPolicyService) expandRuleByService(obj *v1alpha1.Security // If portAddress contains a list of IPs, we should build an ip set group for the rule. if len(portAddress.IPs) > 0 { ruleIPSetGroup := service.buildRuleIPSetGroup(obj, rule, nsxRule, portAddress.IPs, ruleIdx) - groupPath := fmt.Sprintf( - "/infra/domains/%s/groups/%s", - getDomain(service), - *ruleIPSetGroup.Id, - ) + + // In VPC network, NSGroup with IPAddressExpression type can be supported in VCP level as well. + groupPath, err := service.buildRulePeerGroupPath(obj, *ruleIPSetGroup.Id, false) + if err != nil { + return nil, nil, err + } nsxRule.DestinationGroups = []string{groupPath} - log.V(2).Info("built ruleIPSetGroup", "ruleIPSetGroup", ruleIPSetGroup) + log.V(1).Info("built ruleIPSetGroup", "ruleIPSetGroup", ruleIPSetGroup) nsxGroups = append(nsxGroups, ruleIPSetGroup) } - log.V(2).Info("built rule by service entry", "rule", nsxRule) + log.V(1).Info("built rule by service entry", "rule", nsxRule) return nsxGroups, nsxRule, nil } // Resolve a named port to port number by rule and policy selector. // e.g. "http" -> [{"80":['1.1.1.1', '2.2.2.2']}, {"443":['3.3.3.3']}] func (service *SecurityPolicyService) resolveNamedPort(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, - spPort v1alpha1.SecurityPolicyPort) ([]nsxutil.PortAddress, error) { + spPort v1alpha1.SecurityPolicyPort, +) ([]nsxutil.PortAddress, error) { var portAddress []nsxutil.PortAddress podSelectors, err := service.getPodSelectors(obj, rule) @@ -184,14 +190,16 @@ func (service *SecurityPolicyService) resolvePodPort(pod v1.Pod, spPort *v1alpha // Build an ip set group for NSX. func (service *SecurityPolicyService) buildRuleIPSetGroup(obj *v1alpha1.SecurityPolicy, rule *v1alpha1.SecurityPolicyRule, ruleModel *model.Rule, - ips []string, ruleIdx int) *model.Group { + ips []string, ruleIdx int, +) *model.Group { ipSetGroup := model.Group{} ipSetGroupID := fmt.Sprintf("%s_ipset", *ruleModel.Id) ipSetGroup.Id = &ipSetGroupID ipSetGroupName := fmt.Sprintf("%s-ipset", *ruleModel.DisplayName) ipSetGroup.DisplayName = &ipSetGroupName - peerTags := service.BuildPeerTags(obj, &rule.Destinations, ruleIdx) + // IPSetGroup is always destinaton group for named port + peerTags := service.BuildPeerTags(obj, &rule.Destinations, ruleIdx, false, false) ipSetGroup.Tags = peerTags addresses := data.NewListValue() diff --git a/pkg/nsx/services/securitypolicy/expand_test.go b/pkg/nsx/services/securitypolicy/expand_test.go index 98066cf84..15c0600e8 100644 --- a/pkg/nsx/services/securitypolicy/expand_test.go +++ b/pkg/nsx/services/securitypolicy/expand_test.go @@ -59,7 +59,7 @@ func TestSecurityPolicyService_buildRuleIPGroup(t *testing.T) { var s *SecurityPolicyService patches := gomonkey.ApplyMethod(reflect.TypeOf(s), "BuildPeerTags", - func(s *SecurityPolicyService, v *v1alpha1.SecurityPolicy, p *[]v1alpha1.SecurityPolicyPeer, i int) []model.Tag { + func(s *SecurityPolicyService, v *v1alpha1.SecurityPolicy, p *[]v1alpha1.SecurityPolicyPeer, i int, isSource, groupShared bool) []model.Tag { peerTags := []model.Tag{ {Scope: nil, Tag: nil}, } diff --git a/pkg/nsx/services/securitypolicy/firewall.go b/pkg/nsx/services/securitypolicy/firewall.go index 14796bafb..4c2345fa1 100644 --- a/pkg/nsx/services/securitypolicy/firewall.go +++ b/pkg/nsx/services/securitypolicy/firewall.go @@ -20,6 +20,7 @@ var ( ResourceTypeSecurityPolicy = common.ResourceTypeSecurityPolicy ResourceTypeRule = common.ResourceTypeRule ResourceTypeGroup = common.ResourceTypeGroup + ResourceTypeShare = common.ResourceTypeShare NewConverter = common.NewConverter ) @@ -28,6 +29,12 @@ type SecurityPolicyService struct { securityPolicyStore *SecurityPolicyStore ruleStore *RuleStore groupStore *GroupStore + shareStore *ShareStore +} + +type ProjectShare struct { + shareGroup *model.Group + share *model.Share } // InitializeSecurityPolicy sync NSX resources @@ -36,7 +43,7 @@ func InitializeSecurityPolicy(service common.Service) (*SecurityPolicyService, e wgDone := make(chan bool) fatalErrors := make(chan error) - wg.Add(3) + wg.Add(4) securityPolicyService := &SecurityPolicyService{Service: service} @@ -55,10 +62,15 @@ func InitializeSecurityPolicy(service common.Service) (*SecurityPolicyService, e Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), BindingType: model.RuleBindingType(), }} + securityPolicyService.shareStore = &ShareStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + BindingType: model.ShareBindingType(), + }} - go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSecurityPolicy, securityPolicyService.securityPolicyStore) - go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeGroup, securityPolicyService.groupStore) - go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, securityPolicyService.ruleStore) + go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSecurityPolicy, nil, securityPolicyService.securityPolicyStore) + go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeGroup, nil, securityPolicyService.groupStore) + go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, nil, securityPolicyService.ruleStore) + go securityPolicyService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeShare, nil, securityPolicyService.shareStore) go func() { wg.Wait() @@ -77,7 +89,7 @@ func InitializeSecurityPolicy(service common.Service) (*SecurityPolicyService, e } func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1.SecurityPolicy) error { - nsxSecurityPolicy, nsxGroups, err := service.buildSecurityPolicy(obj) + nsxSecurityPolicy, nsxGroups, nsxProjectShares, err := service.buildSecurityPolicy(obj) if err != nil { log.Error(err, "failed to build SecurityPolicy") return err @@ -98,7 +110,7 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 changedGroups, staleGroups := ComparableToGroups(changed), ComparableToGroups(stale) if !isChanged && len(changedRules) == 0 && len(staleRules) == 0 && len(changedGroups) == 0 && len(staleGroups) == 0 { - log.Info("security policy, rules and groups are not changed, skip updating them", "nsxSecurityPolicy.Id", nsxSecurityPolicy.Id) + log.Info("securityPolicy, rules and groups are not changed, skip updating them", "nsxSecurityPolicy.Id", nsxSecurityPolicy.Id) return nil } @@ -127,11 +139,70 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 // WrapHighLevelSecurityPolicy will modify the input security policy, so we need to make a copy for the following store update. finalSecurityPolicyCopy := *finalSecurityPolicy finalSecurityPolicyCopy.Rules = finalSecurityPolicy.Rules - infraSecurityPolicy, err := service.WrapHierarchySecurityPolicy(finalSecurityPolicy, finalGroups) - if err != nil { - return err + + if isVpcEnabled(service) { + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return err + } + + var finalProjectGroups *[]model.Group = nil + var finalProjectShares *[]model.Share = nil + + if len(*nsxProjectShares) != 0 { + projectGroups := make([]model.Group, 0) + projectShares := make([]model.Share, 0) + // create/update nsx project shares and nsx project level groups + for i := len(*nsxProjectShares) - 1; i >= 0; i-- { + projectGroups = append(projectGroups, *((*nsxProjectShares)[i].shareGroup)) + projectShares = append(projectShares, *((*nsxProjectShares)[i].share)) + } + + // 1.Create/update project level groups + finalProjectGroups, err = service.createOrUpdateProjectGroups(obj, projectGroups) + if err != nil { + log.Error(err, "failed to create or update project level groups") + return err + } + + // 2.Create/update project shares + finalProjectShares, err = service.createOrUpdateProjectShares(obj, projectShares) + if err != nil { + log.Error(err, "failed to create or update project share") + return err + } + } + + orgRoot, err := service.WrapHierarchyVpcSecurityPolicy(finalSecurityPolicy, finalGroups, vpcInfo) + if err != nil { + log.Error(err, "failed to wrap SecurityPolicy in VPC") + return err + } + // 3.Create/update SecurityPolicy, groups and rules under VPC path + err = service.NSXClient.OrgRootClient.Patch(*orgRoot, &EnforceRevisionCheckParam) + if err != nil { + log.Error(err, "failed to create or update SecurityPolicy") + return err + } + + if (finalProjectGroups != nil) && len(*finalProjectGroups) != 0 { + err = service.groupStore.Apply(finalProjectGroups) + if err != nil { + return err + } + } + + if (finalProjectShares != nil) && len(*finalProjectShares) != 0 { + err = service.shareStore.Apply(finalProjectShares) + } + } else { + infraSecurityPolicy, err := service.WrapHierarchySecurityPolicy(finalSecurityPolicy, finalGroups) + if err != nil { + log.Error(err, "failed to wrap SecurityPolicy") + return err + } + err = service.NSXClient.InfraClient.Patch(*infraSecurityPolicy, &EnforceRevisionCheckParam) } - err = service.NSXClient.InfraClient.Patch(*infraSecurityPolicy, &EnforceRevisionCheckParam) if err != nil { return err } @@ -139,39 +210,57 @@ func (service *SecurityPolicyService) CreateOrUpdateSecurityPolicy(obj *v1alpha1 // The steps below know how to deal with CR, if there is MarkedForDelete, then delete it from store, // otherwise add or update it to store. if isChanged { - err = service.securityPolicyStore.Operate(&finalSecurityPolicyCopy) + err = service.securityPolicyStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } } if !(len(changedRules) == 0 && len(staleRules) == 0) { - err = service.ruleStore.Operate(&finalSecurityPolicyCopy) + err = service.ruleStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } } if !(len(changedGroups) == 0 && len(staleGroups) == 0) { - err = service.groupStore.Operate(&finalGroups) + err = service.groupStore.Apply(&finalGroups) if err != nil { return err } } - log.Info("successfully created or updated nsxSecurityPolicy", "nsxSecurityPolicy", finalSecurityPolicyCopy) + log.Info("successfully created or updated nsx SecurityPolicy", "nsxSecurityPolicy", finalSecurityPolicyCopy) return nil } -func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}) error { +func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}, isVpcCleanup bool) error { var nsxSecurityPolicy *model.SecurityPolicy + var spNameSpace string + var err error g := make([]model.Group, 0) nsxGroups := &g + var projectShares *[]ProjectShare + nsxProjectShares := make([]model.Share, 0) + nsxProjectGroups := make([]model.Group, 0) switch sp := obj.(type) { + // This case is for normal SecurityPolicy deletion process, which means that SecurityPolicy + // has corresponding nsx SecurityPolicy object case *v1alpha1.SecurityPolicy: - var err error - nsxSecurityPolicy, nsxGroups, err = service.buildSecurityPolicy(sp) + nsxSecurityPolicy, nsxGroups, projectShares, err = service.buildSecurityPolicy(sp) + spNameSpace = sp.ObjectMeta.Namespace if err != nil { - log.Error(err, "failed to build SecurityPolicy") + log.Error(err, "failed to build nsx SecurityPolicy in deleting") return err } + + // Collect project share and project level groups that need to be removed from nsx + // project share and project groups only aviable in VPC network. + for i := len(*projectShares) - 1; i >= 0; i-- { + nsxProjectGroups = append(nsxProjectGroups, *((*projectShares)[i].shareGroup)) + nsxProjectShares = append(nsxProjectShares, *((*projectShares)[i].share)) + } + + // This case is for SecurityPolicy GC process, which means that SecurityPolicy + // doesn't exist in K8s any more but still has corresponding nsx SecurityPolicy object. + // Hence, we use SecurityPolicy's UID here from store instead of K8s SecurityPolicy object case types.UID: securityPolicies := service.securityPolicyStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(sp)) if len(securityPolicies) == 0 { @@ -179,13 +268,44 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}) erro return nil } nsxSecurityPolicy = &securityPolicies[0] + // Get namespace of nsx SecurityPolicy from tags since there is no K8s SecurityPolicy object + for i := len(nsxSecurityPolicy.Tags) - 1; i >= 0; i-- { + if *(nsxSecurityPolicy.Tags[i].Scope) == common.TagScopeNamespace { + spNameSpace = *(nsxSecurityPolicy.Tags[i].Tag) + log.V(1).Info("get namespace with SecurityPolicy index", "namespace", spNameSpace, "UID", string(sp)) + break + } + } groups := service.groupStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(sp)) if len(groups) == 0 { - log.Info("did not get groups with index", "UID", string(sp)) + log.Info("did not get groups with SecurityPolicy index", "UID", string(sp)) } - for _, group := range groups { - *nsxGroups = append(*nsxGroups, group) + + if isVpcEnabled(service) || isVpcCleanup { + for i := len(groups) - 1; i >= 0; i-- { + for j := len(groups[i].Tags) - 1; j >= 0; j-- { + if *(groups[i].Tags[j].Scope) == common.TagScopeProjectGroupShared { + if *(groups[i].Tags[j].Tag) == "true" { + nsxProjectGroups = append(nsxProjectGroups, groups[i]) + } else { + *nsxGroups = append(*nsxGroups, groups[i]) + } + break + } + } + } + shares := service.shareStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(sp)) + if len(shares) == 0 { + log.Info("did not get shares with SecurityPolicy index", "UID", string(sp)) + } + for _, share := range shares { + nsxProjectShares = append(nsxProjectShares, share) + } + } else { + for _, group := range groups { + *nsxGroups = append(*nsxGroups, group) + } } } @@ -200,49 +320,231 @@ func (service *SecurityPolicyService) DeleteSecurityPolicy(obj interface{}) erro // WrapHighLevelSecurityPolicy will modify the input security policy, so we need to make a copy for the following store update. finalSecurityPolicyCopy := *nsxSecurityPolicy finalSecurityPolicyCopy.Rules = nsxSecurityPolicy.Rules - infraSecurityPolicy, err := service.WrapHierarchySecurityPolicy(nsxSecurityPolicy, *nsxGroups) - if err != nil { - return err + + if isVpcEnabled(service) || isVpcCleanup { + vpcInfo, err := getVpcInfo(spNameSpace) + if err != nil { + return err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + + for i := len(nsxProjectGroups) - 1; i >= 0; i-- { + nsxProjectGroups[i].MarkedForDelete = &MarkedForDelete + } + for i := len(nsxProjectShares) - 1; i >= 0; i-- { + nsxProjectShares[i].MarkedForDelete = &MarkedForDelete + } + + // 1.Delete SecurityPolicy, groups and rules under VPC path + orgRoot, err := service.WrapHierarchyVpcSecurityPolicy(nsxSecurityPolicy, *nsxGroups, vpcInfo) + if err != nil { + log.Error(err, "failed to wrap SecurityPolicy in VPC") + return err + } + err = service.NSXClient.OrgRootClient.Patch(*orgRoot, &EnforceRevisionCheckParam) + if err != nil { + log.Error(err, "failed to delete SecurityPolicy") + return err + } + + // 2.Delete nsx project share under project level + if len(nsxProjectShares) != 0 { + projectInfra, err := service.WrapHierarchyProjectShares(nsxProjectShares) + if err != nil { + log.Error(err, "failed to wrap project share") + return err + } + + err = service.NSXClient.ProjectInfraClient.Patch(orgId, projectId, *projectInfra, &EnforceRevisionCheckParam) + if err != nil { + log.Error(err, "failed to delete project shares") + return err + } + } + + // 3.Delete nsx project groups under project level + if len(nsxProjectGroups) != 0 { + projectInfra1, err := service.WrapHierarchyProjectGroups(nsxProjectGroups) + if err != nil { + log.Error(err, "failed to wrap project level groups") + return err + } + + err = service.NSXClient.ProjectInfraClient.Patch(orgId, projectId, *projectInfra1, &EnforceRevisionCheckParam) + if err != nil { + log.Error(err, "failed to delte project level groups") + return err + } + } + + if len(nsxProjectShares) != 0 { + err = service.shareStore.Apply(&nsxProjectShares) + if err != nil { + return err + } + } + if len(nsxProjectGroups) != 0 { + err = service.groupStore.Apply(&nsxProjectGroups) + } + } else { + infraSecurityPolicy, err := service.WrapHierarchySecurityPolicy(nsxSecurityPolicy, *nsxGroups) + if err != nil { + log.Error(err, "failed to wrap SecurityPolicy") + return err + } + err = service.NSXClient.InfraClient.Patch(*infraSecurityPolicy, &EnforceRevisionCheckParam) } - err = service.NSXClient.InfraClient.Patch(*infraSecurityPolicy, &EnforceRevisionCheckParam) if err != nil { return err } - err = service.securityPolicyStore.Operate(nsxSecurityPolicy) + + err = service.securityPolicyStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } - err = service.groupStore.Operate(nsxGroups) + err = service.groupStore.Apply(nsxGroups) if err != nil { return err } - err = service.ruleStore.Operate(&finalSecurityPolicyCopy) + err = service.ruleStore.Apply(&finalSecurityPolicyCopy) if err != nil { return err } - log.Info("successfully deleted nsxSecurityPolicy", "nsxSecurityPolicy", nsxSecurityPolicy) + log.Info("successfully deleted nsx SecurityPolicy", "nsxSecurityPolicy", finalSecurityPolicyCopy) return nil } -func (service *SecurityPolicyService) createOrUpdateGroups(nsxGroups []model.Group) error { +func (service *SecurityPolicyService) createOrUpdateGroups(obj *v1alpha1.SecurityPolicy, nsxGroups []model.Group) error { + var err error = nil for _, group := range nsxGroups { group.MarkedForDelete = nil - err := service.NSXClient.GroupClient.Patch(getDomain(service), *group.Id, group) - if err != nil { - return err + if isVpcEnabled(service) { + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + vpcId := (*vpcInfo).VPCID + + err = service.NSXClient.VpcGroupClient.Patch(orgId, projectId, vpcId, *group.Id, group) + } else { + err = service.NSXClient.GroupClient.Patch(getDomain(service), *group.Id, group) } } - err := service.groupStore.Operate(&nsxGroups) if err != nil { return err } - log.Info("successfully create or update group", "groups", nsxGroups) + err = service.groupStore.Apply(&nsxGroups) + if err != nil { + return err + } + log.Info("successfully create or update groups", "groups", nsxGroups) return nil } +// Create a project group share to share the group with vpc in which SecurityPolicy is +func (service *SecurityPolicyService) createOrUpdateProjectShares(obj *v1alpha1.SecurityPolicy, projectShares []model.Share) (*[]model.Share, error) { + finalShares := make([]model.Share, 0) + + existingShares := service.shareStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(obj.UID)) + + changed, stale := common.CompareResources(SharesToComparable(existingShares), SharesToComparable(projectShares)) + changedShares, staleShares := ComparableToShares(changed), ComparableToShares(stale) + + if len(changedShares) == 0 && len(staleShares) == 0 { + log.Info("project shares are not changed, skip updating them") + return &finalShares, nil + } + + for i := len(staleShares) - 1; i >= 0; i-- { // Don't use range, it would copy the element + staleShares[i].MarkedForDelete = &MarkedForDelete + } + finalShares = append(finalShares, staleShares...) + finalShares = append(finalShares, changedShares...) + + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return nil, err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + + projectInfra, err := service.WrapHierarchyProjectShares(projectShares) + if err != nil { + log.Error(err, "failed to wrap project shares") + return nil, err + } + + err = service.NSXClient.ProjectInfraClient.Patch(orgId, projectId, *projectInfra, &EnforceRevisionCheckParam) + if err != nil { + return nil, err + } + + return &finalShares, nil +} + +func (service *SecurityPolicyService) createOrUpdateProjectGroups(obj *v1alpha1.SecurityPolicy, groups []model.Group) (*[]model.Group, error) { + finalGroups := make([]model.Group, 0) + + existingGroups := service.groupStore.GetByIndex(common.TagScopeSecurityPolicyCRUID, string(obj.UID)) + + changed, stale := common.CompareResources(GroupsToComparable(existingGroups), GroupsToComparable(groups)) + changedGroups, staleGroups := ComparableToGroups(changed), ComparableToGroups(stale) + + if len(changedGroups) == 0 && len(staleGroups) == 0 { + log.Info("project groups are not changed, skip updating them") + return nil, nil + } + + for i := len(staleGroups) - 1; i >= 0; i-- { // Don't use range, it would copy the element + staleGroups[i].MarkedForDelete = &MarkedForDelete + } + finalGroups = append(finalGroups, staleGroups...) + finalGroups = append(finalGroups, changedGroups...) + + vpcInfo, err := getVpcInfo(obj.ObjectMeta.Namespace) + if err != nil { + return nil, err + } + orgId := (*vpcInfo).OrgID + projectId := (*vpcInfo).ProjectID + + projectInfra, err := service.WrapHierarchyProjectGroups(finalGroups) + if err != nil { + log.Error(err, "failed to wrap project level groups") + return nil, err + } + + err = service.NSXClient.ProjectInfraClient.Patch(orgId, projectId, *projectInfra, &EnforceRevisionCheckParam) + if err != nil { + return nil, err + } + + return &finalGroups, nil +} + func (service *SecurityPolicyService) ListSecurityPolicyID() sets.String { + // List SeurityPolicyID to which groups resources are associated in group store groupSet := service.groupStore.ListIndexFuncValues(common.TagScopeSecurityPolicyCRUID) + // List SeurityPolicyID to which share resources are associated in share store + shareSet := service.shareStore.ListIndexFuncValues(common.TagScopeSecurityPolicyCRUID) policySet := service.securityPolicyStore.ListIndexFuncValues(common.TagScopeSecurityPolicyCRUID) - return groupSet.Union(policySet) + + return groupSet.Union(policySet).Union(shareSet) +} + +func (service *SecurityPolicyService) Cleanup() error { + // Delete all the security policies in store + uids := service.ListSecurityPolicyID() + log.Info("cleaning up security policies", "count", len(uids)) + for uid := range uids { + err := service.DeleteSecurityPolicy(types.UID(uid), true) + if err != nil { + return err + } + } + return nil } diff --git a/pkg/nsx/services/securitypolicy/firewall_test.go b/pkg/nsx/services/securitypolicy/firewall_test.go index 145f1818f..1c00189a8 100644 --- a/pkg/nsx/services/securitypolicy/firewall_test.go +++ b/pkg/nsx/services/securitypolicy/firewall_test.go @@ -5,6 +5,7 @@ package securitypolicy import ( "reflect" + "strings" "testing" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" @@ -25,10 +26,11 @@ var ( directionIn = v1alpha1.RuleDirectionIn directionOut = v1alpha1.RuleDirectionOut - tagScopeGroupType = common.TagScopeGroupType - + tagScopeVersion = common.TagScopeVersion + tagScopeGroupType = common.TagScopeGroupType tagScopeCluster = common.TagScopeCluster tagScopeNamespace = common.TagScopeNamespace + tagScopeNamespaceUID = common.TagScopeNamespaceUID tagScopeSecurityPolicyCRName = common.TagScopeSecurityPolicyCRName tagScopeSecurityPolicyCRUID = common.TagScopeSecurityPolicyCRUID tagScopeRuleID = common.TagScopeRuleID @@ -56,8 +58,11 @@ var ( nsxDirectionOut = "OUT" nsxActionDrop = "DROP" cluster = "k8scl-one" - tagValueScope = "scope" + tagValueVersion = strings.Join(common.TagValueVersion, ".") + tagValueGroupScope = common.TagValueGroupScope + tagValueGroupSource = common.TagValueGroupSrc tagValueNS = "ns1" + tagValueNSUID = "us1UID" tagValuePolicyCRName = "spA" tagValuePolicyCRUID = "uidA" tagValuePodSelectorHash = "a42321575d78a6c340c6963c7a82c86c7217f847" @@ -247,6 +252,10 @@ var ( } basicTags = []model.Tag{ + { + Scope: &tagScopeVersion, + Tag: &tagValueVersion, + }, { Scope: &tagScopeCluster, Tag: &cluster, @@ -255,6 +264,10 @@ var ( Scope: &tagScopeNamespace, Tag: &tagValueNS, }, + { + Scope: &tagScopeNamespaceUID, + Tag: &tagValueNSUID, + }, { Scope: &tagScopeSecurityPolicyCRName, Tag: &tagValuePolicyCRName, @@ -282,6 +295,10 @@ func TestListSecurityPolicyID(t *testing.T) { Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), BindingType: model.RuleBindingType(), }} + service.shareStore = &ShareStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}), + BindingType: model.ShareBindingType(), + }} group := model.Group{} scope := "nsx-op/security_policy_cr_uid" @@ -317,6 +334,17 @@ func TestListSecurityPolicyID(t *testing.T) { t.Fatalf("Failed to add policy to store: %v", err) } + id3 := "shareId" + uuid3 := "shareIdUID" + share := model.Share{} + share.Id = &id1 + share.UniqueId = &uuid3 + share.Tags = []model.Tag{{Scope: &scope, Tag: &id3}} + err = service.shareStore.Add(share) + if err != nil { + t.Fatalf("Failed to add share to store: %v", err) + } + tests := []struct { name string want sets.String @@ -332,6 +360,7 @@ func TestListSecurityPolicyID(t *testing.T) { tests[0].want.Insert(id) tests[0].want.Insert(id1) tests[0].want.Insert(id2) + tests[0].want.Insert(id3) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := service.ListSecurityPolicyID() diff --git a/pkg/nsx/services/securitypolicy/parse.go b/pkg/nsx/services/securitypolicy/parse.go index 9d05d9fb1..60af54833 100644 --- a/pkg/nsx/services/securitypolicy/parse.go +++ b/pkg/nsx/services/securitypolicy/parse.go @@ -2,8 +2,11 @@ package securitypolicy import ( "errors" + "fmt" "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" "github.com/vmware-tanzu/nsx-operator/pkg/util" ) @@ -12,10 +15,13 @@ var validRuleActions = []string{ util.ToUpper(v1alpha1.RuleActionDrop), util.ToUpper(v1alpha1.RuleActionReject), } -var ruleDirectionIngress = util.ToUpper(v1alpha1.RuleDirectionIngress) -var ruleDirectionIn = util.ToUpper(v1alpha1.RuleDirectionIn) -var ruleDirectionEgress = util.ToUpper(v1alpha1.RuleDirectionEgress) -var ruleDirectionOut = util.ToUpper(v1alpha1.RuleDirectionOut) + +var ( + ruleDirectionIngress = util.ToUpper(v1alpha1.RuleDirectionIngress) + ruleDirectionIn = util.ToUpper(v1alpha1.RuleDirectionIn) + ruleDirectionEgress = util.ToUpper(v1alpha1.RuleDirectionEgress) + ruleDirectionOut = util.ToUpper(v1alpha1.RuleDirectionOut) +) func getRuleAction(rule *v1alpha1.SecurityPolicyRule) (string, error) { ruleAction := util.ToUpper(*rule.Action) @@ -44,3 +50,61 @@ func getCluster(service *SecurityPolicyService) string { func getDomain(service *SecurityPolicyService) string { return getCluster(service) } + +func getVpcProjectDomain() string { + return "default" +} + +func isVpcEnabled(service *SecurityPolicyService) bool { + return service.NSXConfig.EnableVPCNetwork +} + +func getScopeCluserTag(service *SecurityPolicyService) string { + if isVpcEnabled(service) { + return common.TagScopeCluster + } else { + return common.TagScopeNCPCluster + } +} + +func getScopePodTag(service *SecurityPolicyService) string { + if isVpcEnabled(service) { + return common.TagScopePodUID + } else { + return common.TagScopeNCPPod + } +} + +func getScopeVMInterfaceTag(service *SecurityPolicyService) string { + if isVpcEnabled(service) { + return common.TagScopeSubnetPortCRUID + } else { + return common.TagScopeNCPVNETInterface + } +} + +func getScopeNamespaceUIDTag(service *SecurityPolicyService, isVMNameSpace bool) string { + if isVpcEnabled(service) { + if isVMNameSpace { + return common.TagScopeVMNamespaceUID + } else { + return common.TagScopeNamespaceUID + } + } else { + if isVMNameSpace { + return common.TagScopeNCPVIFProjectUID + } else { + return common.TagScopeNCPProjectUID + } + } +} + +func getVpcInfo(spNameSpace string) (*common.VPCResourceInfo, error) { + VPCInfo := commonctl.ServiceMediator.ListVPCInfo(spNameSpace) + if len(VPCInfo) == 0 { + errorMsg := fmt.Sprintf("there is no VPC info found for namespace %s", spNameSpace) + err := errors.New(errorMsg) + return nil, err + } + return &VPCInfo[0], nil +} diff --git a/pkg/nsx/services/securitypolicy/store.go b/pkg/nsx/services/securitypolicy/store.go index 441ea268e..a986ff31f 100644 --- a/pkg/nsx/services/securitypolicy/store.go +++ b/pkg/nsx/services/securitypolicy/store.go @@ -17,6 +17,8 @@ func keyFunc(obj interface{}) (string, error) { return *v.Id, nil case model.Rule: return *v.Id, nil + case model.Share: + return *v.Id, nil default: return "", errors.New("keyFunc doesn't support unknown type") } @@ -33,6 +35,8 @@ func indexFunc(obj interface{}) ([]string, error) { return filterTag(o.Tags), nil case model.Rule: return filterTag(o.Tags), nil + case model.Share: + return filterTag(o.Tags), nil default: return res, errors.New("indexFunc doesn't support unknown type") } @@ -52,13 +56,13 @@ func indexGroupFunc(obj interface{}) ([]string, error) { res := make([]string, 0, 5) switch o := obj.(type) { case model.Group: - return filterGroupTag(o.Tags), nil + return filterRuleTag(o.Tags), nil default: return res, errors.New("indexGroupFunc doesn't support unknown type") } } -var filterGroupTag = func(v []model.Tag) []string { +var filterRuleTag = func(v []model.Tag) []string { res := make([]string, 0, 5) for _, tag := range v { if *tag.Scope == common.TagScopeRuleID { @@ -83,7 +87,12 @@ type GroupStore struct { common.ResourceStore } -func (securityPolicyStore *SecurityPolicyStore) Operate(i interface{}) error { +// ShareStore is a store for project shares referenced by security policy rule +type ShareStore struct { + common.ResourceStore +} + +func (securityPolicyStore *SecurityPolicyStore) Apply(i interface{}) error { if i == nil { return nil } @@ -122,7 +131,7 @@ func (securityPolicyStore *SecurityPolicyStore) GetByIndex(key string, value str return securityPolicies } -func (ruleStore *RuleStore) Operate(i interface{}) error { +func (ruleStore *RuleStore) Apply(i interface{}) error { sp := i.(*model.SecurityPolicy) for _, rule := range sp.Rules { if rule.MarkedForDelete != nil && *rule.MarkedForDelete { @@ -151,7 +160,7 @@ func (ruleStore *RuleStore) GetByIndex(key string, value string) []model.Rule { return rules } -func (groupStore *GroupStore) Operate(i interface{}) error { +func (groupStore *GroupStore) Apply(i interface{}) error { gs := i.(*[]model.Group) for _, group := range *gs { if group.MarkedForDelete != nil && *group.MarkedForDelete { @@ -179,3 +188,41 @@ func (groupStore *GroupStore) GetByIndex(key string, value string) []model.Group } return groups } + +func (shareStore *ShareStore) Apply(i interface{}) error { + shares := i.(*[]model.Share) + for _, share := range *shares { + if share.MarkedForDelete != nil && *share.MarkedForDelete { + err := shareStore.Delete(share) + log.V(1).Info("delete share from store", "share", share) + if err != nil { + return err + } + } else { + err := shareStore.Add(share) + log.V(1).Info("add share to store", "group", share) + if err != nil { + return err + } + } + } + return nil +} + +func (shareStore *ShareStore) GetByKey(key string) *model.Share { + var share model.Share + obj := shareStore.ResourceStore.GetByKey(key) + if obj != nil { + share = obj.(model.Share) + } + return &share +} + +func (shareStore *ShareStore) GetByIndex(key string, value string) []model.Share { + shares := make([]model.Share, 0) + objs := shareStore.ResourceStore.GetByIndex(key, value) + for _, share := range objs { + shares = append(shares, share.(model.Share)) + } + return shares +} diff --git a/pkg/nsx/services/securitypolicy/store_test.go b/pkg/nsx/services/securitypolicy/store_test.go index fa2133d6a..432884352 100644 --- a/pkg/nsx/services/securitypolicy/store_test.go +++ b/pkg/nsx/services/securitypolicy/store_test.go @@ -152,7 +152,9 @@ func Test_InitializeRuleStore(t *testing.T) { }) defer patches2.Reset() - service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, ruleStore) + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeRule, nil, ruleStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, []string{"11111"}, ruleStore.ListKeys()) } func Test_InitializeGroupStore(t *testing.T) { @@ -201,7 +203,9 @@ func Test_InitializeGroupStore(t *testing.T) { }) defer patches2.Reset() - service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeGroup, groupStore) + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeGroup, nil, groupStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, []string{"11111"}, groupStore.ListKeys()) } func Test_InitializeSecurityPolicyStore(t *testing.T) { @@ -250,10 +254,12 @@ func Test_InitializeSecurityPolicyStore(t *testing.T) { }) defer patches2.Reset() - service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSecurityPolicy, securityPolicyStore) + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSecurityPolicy, nil, securityPolicyStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, []string{"11111"}, securityPolicyStore.ListKeys()) } -func TestSecurityPolicyStore_Operate(t *testing.T) { +func TestSecurityPolicyStore_Apply(t *testing.T) { securityPolicyCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) resourceStore := common.ResourceStore{ Indexer: securityPolicyCacheIndexer, @@ -272,12 +278,12 @@ func TestSecurityPolicyStore_Operate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.wantErr(t, securityPolicyStore.Operate(tt.args.i), fmt.Sprintf("Operate(%v)", tt.args.i)) + tt.wantErr(t, securityPolicyStore.Apply(tt.args.i), fmt.Sprintf("Apply(%v)", tt.args.i)) }) } } -func TestRuleStore_Operate(t *testing.T) { +func TestRuleStore_Apply(t *testing.T) { ruleCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSecurityPolicyCRUID: indexFunc}) resourceStore := common.ResourceStore{ Indexer: ruleCacheIndexer, @@ -328,7 +334,7 @@ func TestRuleStore_Operate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.wantErr(t, ruleStore.Operate(tt.args.i), fmt.Sprintf("Operate(%v)", tt.args.i)) + tt.wantErr(t, ruleStore.Apply(tt.args.i), fmt.Sprintf("Apply(%v)", tt.args.i)) }) } } @@ -353,7 +359,7 @@ func TestGroupStore_Operator(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.wantErr(t, groupStore.Operate(tt.args.i), fmt.Sprintf("Operate(%v)", tt.args.i)) + tt.wantErr(t, groupStore.Apply(tt.args.i), fmt.Sprintf("Apply(%v)", tt.args.i)) }) } } diff --git a/pkg/nsx/services/securitypolicy/wrap.go b/pkg/nsx/services/securitypolicy/wrap.go index 3a6fed59f..e3d8f3ce4 100644 --- a/pkg/nsx/services/securitypolicy/wrap.go +++ b/pkg/nsx/services/securitypolicy/wrap.go @@ -13,7 +13,7 @@ import ( // We use infra patch API in hierarchical mode to create/update/delete entire or part of intent hierarchy, // for this convenience we can no longer CRUD CR separately, and reduce the number of API calls to NSX-T. -// WrapHierarchySecurityPolicy Wrap the security policy with groups and rules into a hierarchy security policy for InfraClient to patch. +// WrapHierarchySecurityPolicy wrap the security policy with groups and rules into a hierarchy security policy for InfraClient to patch. func (service *SecurityPolicyService) WrapHierarchySecurityPolicy(sp *model.SecurityPolicy, gs []model.Group) (*model.Infra, error) { rulesChildren, err := service.wrapRules(sp.Rules) if err != nil { @@ -34,8 +34,8 @@ func (service *SecurityPolicyService) WrapHierarchySecurityPolicy(sp *model.Secu return nil, err } resourceReferenceChildren = append(resourceReferenceChildren, groupsChildren...) - - infraChildren, err := service.wrapResourceReference(resourceReferenceChildren) + domainId := getDomain(service) + infraChildren, err := service.wrapDomainResource(resourceReferenceChildren, domainId) if err != nil { return nil, err } @@ -47,7 +47,7 @@ func (service *SecurityPolicyService) WrapHierarchySecurityPolicy(sp *model.Secu } func (service *SecurityPolicyService) wrapInfra(children []*data.StructValue) (*model.Infra, error) { - // This is the outermost layer of the hierarchy security policy. + // This is the outermost layer of the hierarchy infra client. // It doesn't need ID field. infraType := "Infra" infraObj := model.Infra{ @@ -57,10 +57,10 @@ func (service *SecurityPolicyService) wrapInfra(children []*data.StructValue) (* return &infraObj, nil } -func (service *SecurityPolicyService) wrapResourceReference(children []*data.StructValue) ([]*data.StructValue, error) { - var resourceReferenceChildren []*data.StructValue +func (service *SecurityPolicyService) wrapDomainResource(children []*data.StructValue, domainId string) ([]*data.StructValue, error) { + var domainChildren []*data.StructValue targetType := "Domain" - id := getDomain(service) + id := domainId childDomain := model.ChildResourceReference{ Id: &id, ResourceType: "ChildResourceReference", @@ -71,8 +71,8 @@ func (service *SecurityPolicyService) wrapResourceReference(children []*data.Str if len(errors) > 0 { return nil, errors[0] } - resourceReferenceChildren = append(resourceReferenceChildren, dataValue.(*data.StructValue)) - return resourceReferenceChildren, nil + domainChildren = append(domainChildren, dataValue.(*data.StructValue)) + return domainChildren, nil } func (service *SecurityPolicyService) wrapRules(rules []model.Rule) ([]*data.StructValue, error) { @@ -128,3 +128,172 @@ func (service *SecurityPolicyService) wrapSecurityPolicy(sp *model.SecurityPolic securityPolicyChildren = append(securityPolicyChildren, dataValue.(*data.StructValue)) return securityPolicyChildren, nil } + +// WrapHierarchyVpcSecurityPolicy wrap the security policy with groups and rules into a hierarchy SecurityPolicy for OrgRootClient to patch. +func (service *SecurityPolicyService) WrapHierarchyVpcSecurityPolicy(sp *model.SecurityPolicy, gs []model.Group, + vpcInfo *common.VPCResourceInfo, +) (*model.OrgRoot, error) { + orgID := (*vpcInfo).OrgID + projectID := (*vpcInfo).ProjectID + vpcID := (*vpcInfo).VPCID + + if orgRoot, err := service.wrapOrgRoot(sp, gs, orgID, projectID, vpcID); err != nil { + return nil, err + } else { + return orgRoot, nil + } +} + +func (service *SecurityPolicyService) wrapOrgRoot(sp *model.SecurityPolicy, gs []model.Group, + orgID, projectID, vpcID string, +) (*model.OrgRoot, error) { + // This is the outermost layer of the hierarchy orgRoot client in VPC mode. + // It doesn't need ID field. + resourceType := common.ResourceTypeOrgRoot + children, err := service.wrapOrg(sp, gs, orgID, projectID, vpcID) + if err != nil { + return nil, err + } + orgRoot := model.OrgRoot{ + Children: children, + ResourceType: &resourceType, + } + return &orgRoot, nil +} + +func (service *SecurityPolicyService) wrapOrg(sp *model.SecurityPolicy, gs []model.Group, + orgID, projectID, vpcID string, +) ([]*data.StructValue, error) { + children, err := service.wrapProject(sp, gs, projectID, vpcID) + if err != nil { + return nil, err + } + targetType := common.ResourceTypeOrg + childProject := model.ChildResourceReference{ + Id: &orgID, + ResourceType: "ChildResourceReference", + TargetType: &targetType, + Children: children, + } + dataValue, errors := NewConverter().ConvertToVapi(childProject, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SecurityPolicyService) wrapProject(sp *model.SecurityPolicy, gs []model.Group, + projectID, vpcID string, +) ([]*data.StructValue, error) { + children, err := service.wrapVPC(sp, gs, vpcID) + if err != nil { + return nil, err + } + targetType := common.ResourceTypeProject + childProject := model.ChildResourceReference{ + Id: &projectID, + ResourceType: "ChildResourceReference", + TargetType: &targetType, + Children: children, + } + dataValue, errors := NewConverter().ConvertToVapi(childProject, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SecurityPolicyService) wrapVPC(sp *model.SecurityPolicy, gs []model.Group, + vpcID string, +) ([]*data.StructValue, error) { + rulesChildren, err := service.wrapRules(sp.Rules) + if err != nil { + return nil, err + } + sp.Rules = nil + sp.Children = rulesChildren + sp.ResourceType = &common.ResourceTypeSecurityPolicy + + securityPolicyChildren, err := service.wrapSecurityPolicy(sp) + if err != nil { + return nil, err + } + var resourceReferenceChildren []*data.StructValue + resourceReferenceChildren = append(resourceReferenceChildren, securityPolicyChildren...) + groupsChildren, err := service.wrapGroups(gs) + if err != nil { + return nil, err + } + resourceReferenceChildren = append(resourceReferenceChildren, groupsChildren...) + + targetType := common.ResourceTypeVpc + childVPC := model.ChildResourceReference{ + Id: &vpcID, + ResourceType: "ChildResourceReference", + TargetType: &targetType, + Children: resourceReferenceChildren, + } + dataValue, errors := NewConverter().ConvertToVapi(childVPC, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SecurityPolicyService) wrapChildShares(shares []model.Share) ([]*data.StructValue, error) { + var sharesChildren []*data.StructValue + resourceType := common.ResourceTypeChildShare + + for _, share := range shares { + childShare := model.ChildShare{ + Id: share.Id, + ResourceType: resourceType, + MarkedForDelete: share.MarkedForDelete, + Share: &share, + } + + dataValue, errors := NewConverter().ConvertToVapi(childShare, model.ChildShareBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + sharesChildren = append(sharesChildren, dataValue.(*data.StructValue)) + } + return sharesChildren, nil +} + +func (service *SecurityPolicyService) WrapHierarchyProjectShares(shares []model.Share) (*model.Infra, error) { + infraChildren, err := service.wrapChildShares(shares) + if err != nil { + return nil, err + } + + // This is the outermost layer of the hierarchy project infra client in VPC mode. + // It doesn't need ID field. + infra, err := service.wrapInfra(infraChildren) + if err != nil { + return nil, err + } + return infra, nil +} + +func (service *SecurityPolicyService) WrapHierarchyProjectGroups(groups []model.Group) (*model.Infra, error) { + var resourceReferenceChildren []*data.StructValue + groupsChildren, err := service.wrapGroups(groups) + if err != nil { + return nil, err + } + resourceReferenceChildren = append(resourceReferenceChildren, groupsChildren...) + domainId := getVpcProjectDomain() + infraChildren, err := service.wrapDomainResource(resourceReferenceChildren, domainId) + if err != nil { + return nil, err + } + + // This is the outermost layer of the hierarchy project infra client in VPC mode. + // It doesn't need ID field. + infra, err := service.wrapInfra(infraChildren) + if err != nil { + return nil, err + } + return infra, nil +} diff --git a/pkg/nsx/services/securitypolicy/wrap_test.go b/pkg/nsx/services/securitypolicy/wrap_test.go index 0dd64e9c2..a0f6db6f0 100644 --- a/pkg/nsx/services/securitypolicy/wrap_test.go +++ b/pkg/nsx/services/securitypolicy/wrap_test.go @@ -14,8 +14,7 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) -type fakeQueryClient struct { -} +type fakeQueryClient struct{} func (_ *fakeQueryClient) List(_ string, _ *string, _ *string, _ *int64, _ *bool, _ *string) (model.SearchResponse, error) { cursor := "2" @@ -189,9 +188,10 @@ func TestSecurityPolicyService_wrapResourceReference(t *testing.T) { {"1", args{[]*data.StructValue{}}, nil, assert.NoError}, } + domainId := getDomain(service) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, _ := service.wrapResourceReference(tt.args.children) + got, _ := service.wrapDomainResource(tt.args.children, domainId) for _, v := range got { r, _ := Converter.ConvertToGolang(v, model.ChildResourceReferenceBindingType()) rc := r.(model.ChildResourceReference) diff --git a/pkg/nsx/services/staticroute/builder.go b/pkg/nsx/services/staticroute/builder.go new file mode 100644 index 000000000..457e29bc3 --- /dev/null +++ b/pkg/nsx/services/staticroute/builder.go @@ -0,0 +1,53 @@ +package staticroute + +import ( + "fmt" + "net" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +func validateStaticRoute(obj *v1alpha1.StaticRoute) error { + ipDict := make(map[string]bool) + for index := range obj.Spec.NextHops { + ip := obj.Spec.NextHops[index].IPAddress + if _, exist := ipDict[ip]; exist { + err := fmt.Errorf("duplicate ip address %s", ip) + log.Error(err, "buildStaticRoute") + return err + } + if value := net.ParseIP(ip); value == nil { + err := fmt.Errorf("invalid IP address: %s", ip) + log.Error(err, "buildStaticRoute") + return err + } + ipDict[ip] = true + } + return nil +} + +func (service *StaticRouteService) buildStaticRoute(obj *v1alpha1.StaticRoute) (*model.StaticRoutes, error) { + if err := validateStaticRoute(obj); err != nil { + return nil, err + } + sr := &model.StaticRoutes{} + sr.Network = &obj.Spec.Network + dis := int64(1) + for index := range obj.Spec.NextHops { + nexthop := model.RouterNexthop{AdminDistance: &dis} + nexthop.IpAddress = &obj.Spec.NextHops[index].IPAddress + sr.NextHops = append(sr.NextHops, nexthop) + } + sr.Id = String(util.GenerateID(string(obj.UID), "sr", "", "")) + sr.DisplayName = String(util.GenerateTruncName(common.MaxNameLength, obj.Name, "sr", "", "", "")) + sr.Tags = service.buildBasicTags(obj) + return sr, nil +} + +func (service *StaticRouteService) buildBasicTags(obj *v1alpha1.StaticRoute) []model.Tag { + return util.BuildBasicTags(service.Service.NSXConfig.Cluster, obj, "") +} diff --git a/pkg/nsx/services/staticroute/builder_test.go b/pkg/nsx/services/staticroute/builder_test.go new file mode 100644 index 000000000..cec16321c --- /dev/null +++ b/pkg/nsx/services/staticroute/builder_test.go @@ -0,0 +1,43 @@ +package staticroute + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" +) + +func TestValidateStaticRoute(t *testing.T) { + obj := &v1alpha1.StaticRoute{} + err := validateStaticRoute(obj) + assert.Equal(t, err, nil) + + ip1 := "10.0.0.1" + obj.Spec.NextHops = []v1alpha1.NextHop{{IPAddress: ip1}, {IPAddress: ip1}} + err = validateStaticRoute(obj) + assert.Equal(t, err, fmt.Errorf("duplicate ip address %s", ip1)) + + ip2 := "10.0.0.0.1" + obj.Spec.NextHops = []v1alpha1.NextHop{{IPAddress: ip1}, {IPAddress: ip2}} + err = validateStaticRoute(obj) + assert.Equal(t, err, fmt.Errorf("invalid IP address: %s", ip2)) +} + +func TestBuildStaticRoute(t *testing.T) { + obj := &v1alpha1.StaticRoute{} + ip1 := "10.0.0.1" + ip2 := "10.0.0.2" + obj.Spec.NextHops = []v1alpha1.NextHop{{IPAddress: ip1}, {IPAddress: ip2}} + obj.ObjectMeta.Name = "teststaticroute" + obj.ObjectMeta.Namespace = "qe" + service := &StaticRouteService{} + service.NSXConfig = &config.NSXOperatorConfig{} + service.NSXConfig.CoeConfig = &config.CoeConfig{} + service.NSXConfig.Cluster = "test_1" + staticroutes, err := service.buildStaticRoute(obj) + assert.Equal(t, err, nil) + assert.Equal(t, len(staticroutes.NextHops), 2) +} diff --git a/pkg/nsx/services/staticroute/compare.go b/pkg/nsx/services/staticroute/compare.go new file mode 100644 index 000000000..2d96cbefd --- /dev/null +++ b/pkg/nsx/services/staticroute/compare.go @@ -0,0 +1,28 @@ +package staticroute + +import ( + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/util/sets" +) + +// assume that staticroute doesn't have the same ipaddress, return true if equal +func (service *StaticRouteService) compareStaticRoute(oldStaticRoute *model.StaticRoutes, newStaticRoute *model.StaticRoutes) bool { + if *oldStaticRoute.Network != *newStaticRoute.Network { + return false + } + oldNextHops := oldStaticRoute.NextHops + newNextHops := newStaticRoute.NextHops + if len(oldNextHops) != len(newNextHops) { + return false + } + oldHopsSet := sets.NewString() + for _, addr := range oldNextHops { + oldHopsSet.Insert(*addr.IpAddress) + } + for _, addr := range newNextHops { + if !oldHopsSet.Has(*addr.IpAddress) { + return false + } + } + return true +} diff --git a/pkg/nsx/services/staticroute/staticroute.go b/pkg/nsx/services/staticroute/staticroute.go new file mode 100644 index 000000000..065589f6c --- /dev/null +++ b/pkg/nsx/services/staticroute/staticroute.go @@ -0,0 +1,162 @@ +package staticroute + +import ( + "fmt" + "strings" + "sync" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type StaticRouteService struct { + common.Service + StaticRouteStore *StaticRouteStore +} + +var ( + log = logger.Log + resourceTypeStaticRoute = "StaticRoutes" + String = common.String +) + +// InitializeStaticRoute sync NSX resources +func InitializeStaticRoute(commonService common.Service) (*StaticRouteService, error) { + wg := sync.WaitGroup{} + wgDone := make(chan bool) + fatalErrors := make(chan error) + + wg.Add(1) + staticRouteService := &StaticRouteService{Service: commonService} + staticRouteStore := &StaticRouteStore{} + staticRouteStore.Indexer = cache.NewIndexer(keyFunc, cache.Indexers{ + common.TagScopeStaticRouteCRUID: indexFunc, + }) + staticRouteStore.BindingType = model.StaticRoutesBindingType() + staticRouteService.StaticRouteStore = staticRouteStore + staticRouteService.NSXConfig = commonService.NSXConfig + + go staticRouteService.InitializeResourceStore(&wg, fatalErrors, resourceTypeStaticRoute, nil, staticRouteService.StaticRouteStore) + + go func() { + wg.Wait() + close(wgDone) + }() + + select { + case <-wgDone: + break + case err := <-fatalErrors: + close(fatalErrors) + return staticRouteService, err + } + + return staticRouteService, nil +} + +func (service *StaticRouteService) CreateOrUpdateStaticRoute(namespace string, obj *v1alpha1.StaticRoute) error { + nsxStaticRoute, err := service.buildStaticRoute(obj) + if err != nil { + return err + } + + existingStaticRoute := service.StaticRouteStore.GetByKey(*nsxStaticRoute.Id) + if existingStaticRoute != nil && service.compareStaticRoute(existingStaticRoute, nsxStaticRoute) { + return nil + } + + vpc := commonctl.ServiceMediator.GetVPCsByNamespace(namespace) + if len(vpc) == 0 { + return fmt.Errorf("no vpc found for ns %s", namespace) + } + path := strings.Split(*vpc[0].Path, "/") + err = service.patch(path[2], path[4], *vpc[0].Id, nsxStaticRoute) + if err != nil { + return err + } + staticRoute, err := service.NSXClient.StaticRouteClient.Get(path[2], path[4], *vpc[0].Id, *nsxStaticRoute.Id) + if err != nil { + return err + } + err = service.StaticRouteStore.Add(staticRoute) + if err != nil { + return err + } + return nil +} + +func (service *StaticRouteService) patch(orgId string, projectId string, vpcId string, st *model.StaticRoutes) error { + err := service.NSXClient.StaticRouteClient.Patch(orgId, projectId, vpcId, *st.Id, *st) + if err != nil { + return err + } + return nil +} + +func (service *StaticRouteService) DeleteStaticRouteByPath(orgId string, projectId string, vpcId string, uid string) error { + staticRouteClient := service.NSXClient.StaticRouteClient + staticroute := service.StaticRouteStore.GetByKey(uid) + if staticroute == nil { + return nil + } + + if err := staticRouteClient.Delete(orgId, projectId, vpcId, *staticroute.Id); err != nil { + return err + } + if err := service.StaticRouteStore.Delete(*staticroute); err != nil { + return err + } + + log.Info("successfully deleted NSX StaticRoute", "nsxStaticRoute", *staticroute.Id) + return nil +} +func (service *StaticRouteService) GetUID(staticroute *model.StaticRoutes) *string { + if staticroute == nil { + return nil + } + for _, tag := range staticroute.Tags { + if *tag.Scope == common.TagScopeStaticRouteCRUID { + return tag.Tag + } + } + return nil + +} + +func (service *StaticRouteService) DeleteStaticRoute(namespace string, uid string) error { + vpc := commonctl.ServiceMediator.GetVPCsByNamespace(namespace) + if len(vpc) == 0 { + return nil + } + path := strings.Split(*vpc[0].Path, "/") + return service.DeleteStaticRouteByPath(path[2], path[4], *vpc[0].Id, uid) +} + +func (service *StaticRouteService) ListStaticRoute() []model.StaticRoutes { + staticRoutes := service.StaticRouteStore.List() + staticRouteSet := []model.StaticRoutes{} + for _, staticroute := range staticRoutes { + staticRouteSet = append(staticRouteSet, staticroute.(model.StaticRoutes)) + } + return staticRouteSet +} + +func (service *StaticRouteService) Cleanup() error { + staticRouteSet := service.ListStaticRoute() + log.Info("cleanup staticroute", "count", len(staticRouteSet)) + for _, staticRoute := range staticRouteSet { + path := strings.Split(*staticRoute.Path, "/") + log.Info("removing staticroute", "staticroute path", *staticRoute.Path) + err := service.DeleteStaticRouteByPath(path[2], path[4], path[6], *staticRoute.Id) + if err != nil { + log.Error(err, "remove staticroute failed", "staticroute id", *staticRoute.Id) + return err + } + } + return nil +} diff --git a/pkg/nsx/services/staticroute/staticroute_test.go b/pkg/nsx/services/staticroute/staticroute_test.go new file mode 100644 index 000000000..1245ce706 --- /dev/null +++ b/pkg/nsx/services/staticroute/staticroute_test.go @@ -0,0 +1,213 @@ +package staticroute + +import ( + "reflect" + "sync" + "testing" + + "github.com/agiledragon/gomonkey" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/config" + commonctl "github.com/vmware-tanzu/nsx-operator/pkg/controllers/common" + mocks "github.com/vmware-tanzu/nsx-operator/pkg/mock/staticrouteclient" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/vpc" +) + +var ( + staticrouteName1 = "ns1-staticroute-1" + staticrouteName2 = "ns1-staticroute-2" + staticrouteID1 = "ns-staticroute-uid-1" + staticrouteID2 = "ns-staticroute-uid-2" + IPv4Type = "IPv4" + cluster = "k8scl-one" + tagValueNS = "ns1" + tagScopeStaticRouteCRName = common.TagScopeStaticRouteCRName + tagScopeStaticRouteCRUID = common.TagScopeStaticRouteCRUID + tagValueStaticRouteCRName = "staticrouteA" + tagValueStaticRouteCRUID = "uidA" + tagScopeCluster = common.TagScopeCluster + tagScopeNamespace = common.TagScopeNamespace +) + +type fakeVPCQueryClient struct { +} + +func (qIface *fakeVPCQueryClient) List(_ string, _ string, _ string, _ *string, _ *string, _ *int64, _ *bool, _ *string) (model.SearchResponse, error) { + resultCount := int64(1) + return model.SearchResponse{ + Results: []*data.StructValue{}, + Cursor: nil, ResultCount: &resultCount, + }, nil +} + +type fakeQueryClient struct { +} + +func (qIface *fakeQueryClient) List(queryParam string, cursorParam *string, includedFieldsParam *string, pageSizeParam *int64, sortAscendingParam *bool, sortByParam *string) (model.SearchResponse, error) { + resultCount := int64(1) + return model.SearchResponse{ + Results: []*data.StructValue{}, + Cursor: nil, ResultCount: &resultCount, + }, nil +} + +func createService(t *testing.T) (*StaticRouteService, *gomock.Controller, *mocks.MockStaticRoutesClient) { + config2 := nsx.NewConfig("localhost", "1", "1", []string{}, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, nil, nil, []string{}) + + cluster, _ := nsx.NewCluster(config2) + rc, _ := cluster.NewRestConnector() + + mockCtrl := gomock.NewController(t) + mockStaticRouteclient := mocks.NewMockStaticRoutesClient(mockCtrl) + + staticRouteStore := &StaticRouteStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeStaticRouteCRUID: indexFunc}), + BindingType: model.StaticRoutesBindingType(), + }} + + service := &StaticRouteService{ + Service: common.Service{ + NSXClient: &nsx.Client{ + QueryClient: &fakeQueryClient{}, + StaticRouteClient: mockStaticRouteclient, + RestConnector: rc, + NsxConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + NSXConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + StaticRouteStore: staticRouteStore, + } + return service, mockCtrl, mockStaticRouteclient +} + +func Test_InitializeStaticRouteStore(t *testing.T) { + service, mockController, _ := createService(t) + defer mockController.Finish() + commonService := service.Service + patch := gomonkey.ApplyMethod(reflect.TypeOf(&commonService), "InitializeResourceStore", func(_ *common.Service, wg *sync.WaitGroup, + fatalErrors chan error, resourceTypeValue string, tags []model.Tag, store common.Store) { + wg.Done() + return + }) + defer patch.Reset() + + _, err := InitializeStaticRoute(commonService) + if err != nil { + t.Error(err) + } +} + +func TestStaticRouteService_DeleteStaticRoute(t *testing.T) { + service, mockController, mockStaticRouteclient := createService(t) + defer mockController.Finish() + + var tc *bindings.TypeConverter + patches2 := gomonkey.ApplyMethod(reflect.TypeOf(tc), "ConvertToGolang", + func(_ *bindings.TypeConverter, d data.DataValue, b bindings.BindingType) (interface{}, []error) { + mId, mTag, mScope := "test_id", "test_tag", "test_scope" + m := model.StaticRoutes{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + var j interface{} = m + return j, nil + }) + defer patches2.Reset() + + returnservice, err := InitializeStaticRoute(service.Service) + if err != nil { + t.Error(err) + } + + sr1 := &model.StaticRoutes{} + id := "vpc-1" + sr1.Id = &id + returnservice.StaticRouteStore.Add(*sr1) + + patches := gomonkey.ApplyMethod(reflect.TypeOf(commonctl.ServiceMediator.VPCService), "GetVPCsByNamespace", func(_ *vpc.VPCService, ns string) []model.Vpc { + id := "vpc-1" + return []model.Vpc{{Path: common.String("/orgs/default/projects/project-1/vpcs/vpc-1"), Id: &id}} + }) + + // no record found + mockStaticRouteclient.EXPECT().Delete(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Times(0) + err = returnservice.DeleteStaticRoute("123", "vpc1") + assert.Equal(t, err, nil) + defer patches.Reset() + + // delete record + mockStaticRouteclient.EXPECT().Delete("default", "project-1", "vpc-1", id).Return(nil).Times(1) + err = returnservice.DeleteStaticRoute("123", id) + assert.Equal(t, err, nil) + srs := returnservice.StaticRouteStore.List() + assert.Equal(t, len(srs), 0) +} + +func TestStaticRouteService_CreateorUpdateStaticRoute(t *testing.T) { + service, mockController, mockStaticRouteclient := createService(t) + defer mockController.Finish() + + var tc *bindings.TypeConverter + patches2 := gomonkey.ApplyMethod(reflect.TypeOf(tc), "ConvertToGolang", + func(_ *bindings.TypeConverter, d data.DataValue, b bindings.BindingType) (interface{}, []error) { + mId, mTag, mScope := "test_id", "test_tag", "test_scope" + m := model.StaticRoutes{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + var j interface{} = m + return j, nil + }) + defer patches2.Reset() + + returnservice, err := InitializeStaticRoute(service.Service) + + if err != nil { + t.Error(err) + } + id := "12345678" + sr1 := &v1alpha1.StaticRoute{} + sr1.UID = types.UID(id) + + mockStaticRouteclient.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1) + mId := "test_id" + scope := common.TagScopeStaticRouteCRUID + tag := "test_tag" + m := model.StaticRoutes{ + Id: &mId, + Tags: []model.Tag{{Tag: &tag, Scope: &scope}}, + } + mockStaticRouteclient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(m, nil).Times(2) + patches := gomonkey.ApplyMethod(reflect.TypeOf(commonctl.ServiceMediator.VPCService), "GetVPCsByNamespace", func(_ *vpc.VPCService, ns string) []model.Vpc { + id := "12345678" + return []model.Vpc{{Path: common.String("/orgs/default/projects/project-1/vpcs/vpc-1"), Id: &id}} + }) + defer patches.Reset() + err = returnservice.CreateOrUpdateStaticRoute("test", sr1) + assert.Equal(t, err, nil) + + // no change, update + mockStaticRouteclient.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1) + err = returnservice.CreateOrUpdateStaticRoute("test", sr1) + assert.Equal(t, err, nil) +} diff --git a/pkg/nsx/services/staticroute/store.go b/pkg/nsx/services/staticroute/store.go new file mode 100644 index 000000000..a067ab0eb --- /dev/null +++ b/pkg/nsx/services/staticroute/store.go @@ -0,0 +1,61 @@ +package staticroute + +import ( + "errors" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +// StaticRouteStore is a store for static route +type StaticRouteStore struct { + common.ResourceStore +} + +// keyFunc is used to get the key of a resource, usually, which is the ID of the resource +func keyFunc(obj interface{}) (string, error) { + switch v := obj.(type) { + case model.StaticRoutes: + return *v.Id, nil + default: + return "", errors.New("keyFunc doesn't support unknown type") + } +} + +// indexFunc is used to get index of a resource, usually, which is the UID of the CR controller reconciles, +// index is used to filter out resources which are related to the CR +func indexFunc(obj interface{}) ([]string, error) { + res := make([]string, 0, 5) + switch v := obj.(type) { + case model.StaticRoutes: + return filterTag(v.Tags), nil + default: + break + } + return res, nil +} + +var filterTag = func(v []model.Tag) []string { + res := make([]string, 0, 5) + for _, tag := range v { + if *tag.Scope == common.TagScopeStaticRouteCRUID { + res = append(res, *tag.Tag) + } + } + return res +} + +func (StaticRouteStore *StaticRouteStore) Apply(i interface{}) error { + // not used by staticroute since staticroute doesn't use hierarchy API + return nil +} + +func (StaticRouteStore *StaticRouteStore) GetByKey(key string) *model.StaticRoutes { + obj := StaticRouteStore.ResourceStore.GetByKey(key) + if obj != nil { + staticRoute := obj.(model.StaticRoutes) + return &staticRoute + } + return nil +} diff --git a/pkg/nsx/services/staticroute/store_test.go b/pkg/nsx/services/staticroute/store_test.go new file mode 100644 index 000000000..5b9930172 --- /dev/null +++ b/pkg/nsx/services/staticroute/store_test.go @@ -0,0 +1,300 @@ +package staticroute + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/client-go/tools/cache" + + common "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +func Test_indexFunc(t *testing.T) { + mId, mTag, mScope := "test_id", "test_tag", common.TagScopeStaticRouteCRUID + v := model.StaticRoutes{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + t.Run("1", func(t *testing.T) { + got, _ := indexFunc(v) + if !reflect.DeepEqual(got, []string{"test_tag"}) { + t.Errorf("indexFunc() = %v, want %v", got, model.Tag{Tag: &mTag, Scope: &mScope}) + } + }) +} + +func Test_KeyFunc(t *testing.T) { + Id := "test_id" + v := model.StaticRoutes{Id: &Id} + t.Run("1", func(t *testing.T) { + got, _ := keyFunc(v) + if got != "test_id" { + t.Errorf("keyFunc() = %v, want %v", got, "test_id") + } + }) +} + +func TestStaticRouteStore_CRUDResource(t *testing.T) { + staticRouteCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeStaticRouteCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: staticRouteCacheIndexer, + BindingType: model.StaticRoutesBindingType(), + } + staticRouteStore := &StaticRouteStore{ResourceStore: resourceStore} + type args struct { + i interface{} + } + delete := true + tests := []struct { + name string + args args + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{i: &model.StaticRoutes{Id: common.String("1")}}, assert.NoError}, + {"2", args{i: &model.StaticRoutes{Id: common.String("2"), MarkedForDelete: &delete}}, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, staticRouteStore.Apply(tt.args.i), fmt.Sprintf("CRUDResource(%v)", tt.args.i)) + }) + } +} + +func TestStaticRouteStore_CRUDResource_List(t *testing.T) { + staticRouteCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeStaticRouteCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: staticRouteCacheIndexer, + BindingType: model.StaticRoutesBindingType(), + } + staticRouteStore := &StaticRouteStore{ResourceStore: resourceStore} + type args struct { + i interface{} + j interface{} + } + ns1 := "test-ns-1" + tag1 := []model.Tag{ + { + Scope: &tagScopeCluster, + Tag: &cluster, + }, + { + Scope: &tagScopeNamespace, + Tag: &ns1, + }, + { + Scope: &tagScopeStaticRouteCRName, + Tag: &tagValueStaticRouteCRName, + }, + { + Scope: &tagScopeStaticRouteCRUID, + Tag: &tagValueStaticRouteCRUID, + }, + } + ns2 := "test-ns-2" + tag2 := []model.Tag{ + { + Scope: &tagScopeCluster, + Tag: &cluster, + }, + { + Scope: &tagScopeNamespace, + Tag: &ns2, + }, + { + Scope: &tagScopeStaticRouteCRName, + Tag: &tagValueStaticRouteCRName, + }, + { + Scope: &tagScopeStaticRouteCRUID, + Tag: &tagValueStaticRouteCRUID, + }, + } + staticRoute1 := model.StaticRoutes{ + + DisplayName: &staticrouteName1, + Id: &staticrouteID1, + Tags: tag1, + } + staticRoute2 := model.StaticRoutes{ + + DisplayName: &staticrouteName2, + Id: &staticrouteID2, + Tags: tag2, + } + tests := []struct { + name string + args args + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{i: staticRoute1, j: staticRoute2}, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + staticRouteStore.Add(staticRoute1) + staticRouteStore.Add(staticRoute2) + got := staticRouteStore.List() + if len(got) != 2 { + t.Errorf("size = %v, want %v", len(got), 2) + } + }) + } +} + +func TestStaticRouteStore_GetByKey(t *testing.T) { + staticRouteCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeStaticRouteCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: staticRouteCacheIndexer, + BindingType: model.StaticRoutesBindingType(), + } + + staticRouteStore := &StaticRouteStore{ResourceStore: resourceStore} + type args struct { + i interface{} + j interface{} + } + ns1 := "test-ns-1" + tag1 := []model.Tag{ + { + Scope: &tagScopeCluster, + Tag: &cluster, + }, + { + Scope: &tagScopeNamespace, + Tag: &ns1, + }, + { + Scope: &tagScopeStaticRouteCRName, + Tag: &tagValueStaticRouteCRName, + }, + { + Scope: &tagScopeStaticRouteCRUID, + Tag: &tagValueStaticRouteCRUID, + }, + } + ns2 := "test-ns-2" + tag2 := []model.Tag{ + { + Scope: &tagScopeCluster, + Tag: &cluster, + }, + { + Scope: &tagScopeNamespace, + Tag: &ns2, + }, + { + Scope: &tagScopeStaticRouteCRName, + Tag: &tagValueStaticRouteCRName, + }, + { + Scope: &tagScopeStaticRouteCRUID, + Tag: &tagValueStaticRouteCRUID, + }, + } + staticRoute1 := model.StaticRoutes{ + DisplayName: &staticrouteName1, + Id: &staticrouteID1, + Tags: tag1, + } + staticRoute2 := model.StaticRoutes{ + + DisplayName: &staticrouteName2, + Id: &staticrouteID2, + Tags: tag2, + } + + staticRouteStore.Add(staticRoute1) + staticRouteStore.Add(staticRoute2) + got := staticRouteStore.GetByKey(staticrouteID2) + if *got.Id != staticrouteID2 { + t.Errorf("get id = %v failed", staticrouteID2) + } + + staticRouteStore.Delete(staticRoute2) + got = staticRouteStore.GetByKey(staticrouteID2) + if got != nil { + t.Errorf("id %v should be deleted", *got.Id) + } +} + +/* +func TestStaticRouteStore_GetByIndex(t *testing.T) { + staticRouteCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeStaticRouteCRUID: indexFunc}) + resourceStore := common.ResourceStore{ + Indexer: staticRouteCacheIndexer, + BindingType: model.StaticRoutesBindingType(), + } + + staticRouteStore := &StaticRouteStore{ResourceStore: resourceStore} + type args struct { + i interface{} + j interface{} + } + ns1 := "test-ns-1" + tag1 := []model.Tag{ + { + Scope: &tagScopeCluster, + Tag: &cluster, + }, + { + Scope: &tagScopeNamespace, + Tag: &ns1, + }, + { + Scope: &tagScopeStaticRouteCRName, + Tag: &tagValueStaticRouteCRName, + }, + { + Scope: &tagScopeStaticRouteCRUID, + Tag: &tagValueStaticRouteCRUID, + }, + } + ns2 := "test-ns-2" + tag2 := []model.Tag{ + { + Scope: &tagScopeCluster, + Tag: &cluster, + }, + { + Scope: &tagScopeNamespace, + Tag: &ns2, + }, + { + Scope: &tagScopeStaticRouteCRName, + Tag: &tagValueStaticRouteCRName, + }, + { + Scope: &tagScopeStaticRouteCRUID, + Tag: &tagValueStaticRouteCRUID, + }, + } + staticRoute1 := model.StaticRoutes{ + DisplayName: &staticrouteName1, + Id: &staticrouteID1, + Tags: tag1, + } + staticRoute2 := model.StaticRoutes{ + + DisplayName: &staticrouteName2, + Id: &staticrouteID2, + Tags: tag2, + } + + staticRouteStore.Add(staticRoute1) + staticRouteStore.Add(staticRoute2) + value := staticRouteStore.ListIndexFuncValues(tagValueStaticRouteCRUID) + t.Errorf("the value is %d, %v", len(value), value) + got := staticRouteStore.GetByIndex(tagValueStaticRouteCRUID, string(tagValueStaticRouteCRUID)) + if len(got) != 2 { + t.Errorf("size = %v, want = %v", len(got), 2) + } + + staticRouteStore.Delete(staticRoute2) + got = staticRouteStore.GetByIndex(tagValueStaticRouteCRUID, "test") + if len(got) != 0 { + t.Errorf("size = %v, want = %v", len(got), 0) + } +} +*/ diff --git a/pkg/nsx/services/subnet/builder.go b/pkg/nsx/services/subnet/builder.go new file mode 100644 index 000000000..10629cbcf --- /dev/null +++ b/pkg/nsx/services/subnet/builder.go @@ -0,0 +1,100 @@ +package subnet + +import ( + "github.com/google/uuid" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + String = common.String + Int64 = common.Int64 + Bool = common.Bool +) + +const ( + SUBNETPREFIX = "sub" +) + +func getCluster(service *SubnetService) string { + return service.NSXConfig.Cluster +} + +func (service *SubnetService) BuildSubnetID(subnet *v1alpha1.Subnet) string { + return util.GenerateID(string(subnet.UID), SUBNETPREFIX, "", "") +} + +func (service *SubnetService) buildSubnetSetID(subnetset *v1alpha1.SubnetSet, index string) string { + return util.GenerateID(string(subnetset.UID), SUBNETPREFIX, "", index) +} + +func (service *SubnetService) buildSubnetName(subnet *v1alpha1.Subnet) string { + return util.GenerateTruncName(common.MaxSubnetNameLength, subnet.ObjectMeta.Name, SUBNETPREFIX, "", "", getCluster(service)) +} + +func (service *SubnetService) buildSubnetSetName(subnetset *v1alpha1.SubnetSet, index string) string { + return util.GenerateTruncName(common.MaxSubnetNameLength, subnetset.ObjectMeta.Name, SUBNETPREFIX, index, "", getCluster(service)) +} + +func (service *SubnetService) buildSubnet(obj client.Object, tags []model.Tag) (*model.VpcSubnet, error) { + tags = append(service.buildBasicTags(obj), tags...) + var nsxSubnet *model.VpcSubnet + var staticIpAllocation bool + switch o := obj.(type) { + case *v1alpha1.Subnet: + nsxSubnet = &model.VpcSubnet{ + Id: String(service.BuildSubnetID(o)), + AccessMode: String(util.Capitalize(string(o.Spec.AccessMode))), + DhcpConfig: service.buildDHCPConfig(int64(o.Spec.IPv4SubnetSize - 4)), + DisplayName: String(service.buildSubnetName(o)), + } + staticIpAllocation = o.Spec.AdvancedConfig.StaticIPAllocation.Enable + case *v1alpha1.SubnetSet: + index := uuid.NewString() + nsxSubnet = &model.VpcSubnet{ + Id: String(service.buildSubnetSetID(o, index)), + AccessMode: String(util.Capitalize(string(o.Spec.AccessMode))), + DhcpConfig: service.buildDHCPConfig(int64(o.Spec.IPv4SubnetSize - 4)), + DisplayName: String(service.buildSubnetSetName(o, index)), + } + staticIpAllocation = o.Spec.AdvancedConfig.StaticIPAllocation.Enable + default: + return nil, SubnetTypeError + } + nsxSubnet.Tags = tags + nsxSubnet.AdvancedConfig = &model.SubnetAdvancedConfig{ + StaticIpAllocation: &model.StaticIpAllocation{ + Enabled: &staticIpAllocation, + }, + } + return nsxSubnet, nil +} + +func (service *SubnetService) buildDHCPConfig(poolSize int64) *model.VpcSubnetDhcpConfig { + // Subnet DHCP is used by AVI, not needed for now. We need to explicitly mark enableDhcp = false, + // otherwise Subnet will use DhcpConfig inherited from VPC. + dhcpConfig := &model.VpcSubnetDhcpConfig{ + EnableDhcp: Bool(false), + StaticPoolConfig: &model.StaticPoolConfig{ + // Number of IPs to be reserved in static ip pool. + // By default, if dhcp is enabled then static ipv4 pool size will be zero and all available IPs will be + // reserved in local dhcp pool. Maximum allowed value is 'subnet size - 4'. + Ipv4PoolSize: Int64(poolSize), + }, + } + return dhcpConfig +} + +func (service *SubnetService) buildDNSClientConfig(obj *v1alpha1.DNSClientConfig) *model.DnsClientConfig { + dnsClientConfig := &model.DnsClientConfig{} + dnsClientConfig.DnsServerIps = append(dnsClientConfig.DnsServerIps, obj.DNSServersIPs...) + return dnsClientConfig +} + +func (service *SubnetService) buildBasicTags(obj client.Object) []model.Tag { + return util.BuildBasicTags(getCluster(service), obj, "") +} diff --git a/pkg/nsx/services/subnet/compare.go b/pkg/nsx/services/subnet/compare.go new file mode 100644 index 000000000..34a29c8e0 --- /dev/null +++ b/pkg/nsx/services/subnet/compare.go @@ -0,0 +1,33 @@ +package subnet + +import ( + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type ( + Subnet model.VpcSubnet +) + +type Comparable = common.Comparable + +func (subnet *Subnet) Key() string { + return *subnet.Id +} + +func (subnet *Subnet) Value() data.DataValue { + // IPv4SubnetSize/AccessMode/IPAddresses/DHCPConfig are immutable field, + // Only changes of tags are considered as changed. + // TODO AccessMode may also need to be compared in future. + s := &Subnet{ + Tags: subnet.Tags, + } + dataValue, _ := (*model.VpcSubnet)(s).GetDataValue__() + return dataValue +} + +func SubnetToComparable(subnet *model.VpcSubnet) Comparable { + return (*Subnet)(subnet) +} diff --git a/pkg/nsx/services/subnet/store.go b/pkg/nsx/services/subnet/store.go new file mode 100644 index 000000000..ebe32ac27 --- /dev/null +++ b/pkg/nsx/services/subnet/store.go @@ -0,0 +1,102 @@ +package subnet + +import ( + "errors" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +// keyFunc is used to get the key of a resource, usually, which is the ID of the resource +func keyFunc(obj interface{}) (string, error) { + switch v := obj.(type) { + case model.VpcSubnet: + return *v.Id, nil + default: + return "", errors.New("keyFunc doesn't support unknown type") + } +} + +func filterTag(tags []model.Tag, tagScope string) []string { + var res []string + for _, tag := range tags { + if *tag.Scope == tagScope { + res = append(res, *tag.Tag) + } + } + return res +} + +// subnetIndexFunc is used to filter out NSX Subnets which are tagged with CR UID. +func subnetIndexFunc(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.VpcSubnet: + return filterTag(o.Tags, common.TagScopeSubnetCRUID), nil + default: + return nil, errors.New("subnetIndexFunc doesn't support unknown type") + } +} + +// subnetTypeIndexFunc is used to filter out NSX Subnets which are tagged with subnetcr type. +// TODO, change it to use "nsx-op/subnetset_cr_uid" and "nsx-op/subnet_cr_uid" +func subnetTypeIndexFunc(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.VpcSubnet: + return filterTag(o.Tags, common.TagScopeSubnetCRType), nil + default: + return nil, errors.New("subnetIndexFunc doesn't support unknown type") + } +} + +// subnetIndexFunc is used to filter out NSX Subnets which are tagged with CR UID. +func subnetSetIndexFunc(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.VpcSubnet: + return filterTag(o.Tags, common.TagScopeSubnetSetCRUID), nil + default: + return nil, errors.New("subnetSetIndexFunc doesn't support unknown type") + } +} + +// SubnetStore is a store for subnet. +type SubnetStore struct { + common.ResourceStore +} + +func (subnetStore *SubnetStore) Apply(i interface{}) error { + if i == nil { + return nil + } + subnet := i.(*model.VpcSubnet) + if subnet.MarkedForDelete != nil && *subnet.MarkedForDelete { + if err := subnetStore.Delete(*subnet); err != nil { + return err + } + log.Info("Subnet deleted from store", "Subnet", subnet) + } else { + if err := subnetStore.Add(*subnet); err != nil { + return err + } + log.Info("Subnet added to store", "Subnet", subnet) + } + return nil +} + +func (subnetStore *SubnetStore) GetByIndex(key string, value string) []model.VpcSubnet { + subnets := make([]model.VpcSubnet, 0) + objs := subnetStore.ResourceStore.GetByIndex(key, value) + for _, subnet := range objs { + subnets = append(subnets, subnet.(model.VpcSubnet)) + } + return subnets +} + +func (subnetStore *SubnetStore) GetByKey(key string) *model.VpcSubnet { + obj := subnetStore.ResourceStore.GetByKey(key) + if obj == nil { + return nil + } + subnet := obj.(model.VpcSubnet) + return &subnet +} diff --git a/pkg/nsx/services/subnet/store_test.go b/pkg/nsx/services/subnet/store_test.go new file mode 100644 index 000000000..f0ee57b4d --- /dev/null +++ b/pkg/nsx/services/subnet/store_test.go @@ -0,0 +1,131 @@ +package subnet + +import ( + "fmt" + "reflect" + "sync" + "testing" + + "github.com/agiledragon/gomonkey" + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "k8s.io/client-go/tools/cache" + + "github.com/vmware-tanzu/nsx-operator/pkg/config" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type fakeQueryClient struct { +} + +func (qIface *fakeQueryClient) List(_ string, _ *string, _ *string, _ *int64, _ *bool, _ *string) (model.SearchResponse, error) { + cursor := "2" + resultCount := int64(2) + return model.SearchResponse{ + Results: []*data.StructValue{&data.StructValue{}}, + Cursor: &cursor, ResultCount: &resultCount, + }, nil +} + +func Test_IndexFunc(t *testing.T) { + id, tag, scope := "test_id", "cr_uid", common.TagScopeSubnetCRUID + subnet := model.VpcSubnet{ + Id: &id, + Tags: []model.Tag{{Tag: &tag, Scope: &scope}}, + } + t.Run("1", func(t *testing.T) { + got, _ := subnetIndexFunc(subnet) + if !reflect.DeepEqual(got, []string{"cr_uid"}) { + t.Errorf("subnetCRUIDScopeIndexFunc() = %v, want %v", got, model.Tag{Tag: &tag, Scope: &scope}) + } + }) +} + +func Test_KeyFunc(t *testing.T) { + id := "test_id" + subnet := model.VpcSubnet{Id: &id} + t.Run("1", func(t *testing.T) { + got, _ := keyFunc(subnet) + if got != "test_id" { + t.Errorf("keyFunc() = %v, want %v", got, "test_id") + } + }) +} + +func Test_InitializeSubnetStore(t *testing.T) { + config2 := nsx.NewConfig("localhost", "1", "1", []string{}, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, nil, nil, []string{}) + cluster, _ := nsx.NewCluster(config2) + rc, _ := cluster.NewRestConnector() + + subnetCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSubnetCRUID: subnetIndexFunc}) + service := SubnetService{ + Service: common.Service{ + NSXClient: &nsx.Client{ + QueryClient: &fakeQueryClient{}, + RestConnector: rc, + NsxConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + NSXConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + SubnetStore: &SubnetStore{ResourceStore: common.ResourceStore{ + Indexer: subnetCacheIndexer, + BindingType: model.VpcSubnetBindingType(), + }}, + } + + wg := sync.WaitGroup{} + fatalErrors := make(chan error) + wg.Add(3) + + var tc *bindings.TypeConverter + patches2 := gomonkey.ApplyMethod(reflect.TypeOf(tc), "ConvertToGolang", + func(_ *bindings.TypeConverter, d data.DataValue, b bindings.BindingType) (interface{}, []error) { + mId, mTag, mScope := "test_id", "test_tag", "test_scope" + m := model.VpcSubnet{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + } + var j interface{} = m + return j, nil + }) + defer patches2.Reset() + + service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSubnet, nil, service.SubnetStore) +} + +func TestSubnetStore_Apply(t *testing.T) { + subnetCacheIndexer := cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeSubnetCRUID: subnetIndexFunc}) + resourceStore := common.ResourceStore{ + Indexer: subnetCacheIndexer, + BindingType: model.SecurityPolicyBindingType(), + } + subnetStore := &SubnetStore{ResourceStore: resourceStore} + type args struct { + i interface{} + } + tests := []struct { + name string + args args + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{i: &model.VpcSubnet{Id: common.String("1")}}, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, subnetStore.Apply(tt.args.i), fmt.Sprintf("Apply(%v)", tt.args.i)) + }) + } +} diff --git a/pkg/nsx/services/subnet/subnet.go b/pkg/nsx/services/subnet/subnet.go new file mode 100644 index 000000000..8b83610bc --- /dev/null +++ b/pkg/nsx/services/subnet/subnet.go @@ -0,0 +1,404 @@ +package subnet + +import ( + "context" + "errors" + "fmt" + "os" + "sync" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/realizestate" +) + +var ( + log = logger.Log + MarkedForDelete = true + EnforceRevisionCheckParam = false + ResourceTypeSubnet = common.ResourceTypeSubnet + NewConverter = common.NewConverter + // Default static ip-pool under Subnet. + ipPoolID = "static-ipv4-default" + SubnetTypeError = errors.New("unsupported type") +) + +type SubnetService struct { + common.Service + SubnetStore *SubnetStore +} + +// SubnetParameters stores parameters to CRUD Subnet object +type SubnetParameters struct { + OrgID string + ProjectID string + VPCID string +} + +var subnetService *SubnetService +var lock = &sync.Mutex{} + +// GetSubnetService get singleton SubnetService instance, subnet/subnetset controller share the same instance. +func GetSubnetService(service common.Service) *SubnetService { + if subnetService == nil { + lock.Lock() + defer lock.Unlock() + if subnetService == nil { + var err error + if subnetService, err = InitializeSubnetService(service); err != nil { + log.Error(err, "failed to initialize subnet commonService") + os.Exit(1) + } + } + } + return subnetService +} + +// InitializeSubnetService initialize Subnet service. +func InitializeSubnetService(service common.Service) (*SubnetService, error) { + wg := sync.WaitGroup{} + wgDone := make(chan bool) + fatalErrors := make(chan error) + subnetService := &SubnetService{ + Service: service, + SubnetStore: &SubnetStore{ + ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{ + common.TagScopeSubnetCRUID: subnetIndexFunc, + common.TagScopeSubnetCRType: subnetTypeIndexFunc, + common.TagScopeSubnetSetCRUID: subnetSetIndexFunc, + }), + BindingType: model.VpcSubnetBindingType(), + }, + }, + } + + wg.Add(1) + go subnetService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSubnet, nil, subnetService.SubnetStore) + go func() { + wg.Wait() + close(wgDone) + }() + select { + case <-wgDone: + break + case err := <-fatalErrors: + close(fatalErrors) + return subnetService, err + } + return subnetService, nil +} + +func (service *SubnetService) CreateOrUpdateSubnet(obj client.Object, tags []model.Tag) (string, error) { + vpcList := &v1alpha1.VPCList{} + if err := service.Client.List(context.Background(), vpcList, client.InNamespace(obj.GetNamespace())); err != nil { + log.Error(err, "fail to list VPC", "ns", obj.GetNamespace()) + return "", err + } + if len(vpcList.Items) == 0 { + err := errors.New("no VPC found") + log.Error(err, "", "ns", obj.GetNamespace()) + return "", err + } + vpc := vpcList.Items[0] + vpcInfo, err := common.ParseVPCResourcePath(vpc.Status.NSXResourcePath) + if err != nil { + err := fmt.Errorf("failed to parse NSX VPC path for VPC %s: %s", vpc.UID, err) + return "", err + } + uid := string(obj.GetUID()) + nsxSubnet, err := service.buildSubnet(obj, tags) + if err != nil { + log.Error(err, "failed to build Subnet") + return "", err + } + // Only check whether needs update when obj is v1alpha1.Subnet + if subnet, ok := obj.(*v1alpha1.Subnet); ok { + existingSubnet := service.SubnetStore.GetByKey(service.BuildSubnetID(subnet)) + changed := false + if existingSubnet == nil { + changed = true + } else { + changed = common.CompareResource(SubnetToComparable(existingSubnet), SubnetToComparable(nsxSubnet)) + } + if !changed { + log.Info("subnet not changed, skip updating", "subnet.Id", uid) + return uid, nil + } + } + return service.createOrUpdateSubnet(obj, nsxSubnet, &vpcInfo) +} + +func (service *SubnetService) createOrUpdateSubnet(obj client.Object, nsxSubnet *model.VpcSubnet, vpcInfo *common.VPCResourceInfo) (string, error) { + orgRoot, err := service.WrapHierarchySubnet(nsxSubnet, vpcInfo) + if err != nil { + log.Error(err, "WrapHierarchySubnet failed") + return "", err + } + if err = service.NSXClient.OrgRootClient.Patch(*orgRoot, &EnforceRevisionCheckParam); err != nil { + return "", err + } + // Get Subnet from NSX after patch operation as NSX renders several fields like `path`/`parent_path`. + if *nsxSubnet, err = service.NSXClient.SubnetsClient.Get(vpcInfo.OrgID, vpcInfo.ProjectID, vpcInfo.VPCID, *nsxSubnet.Id); err != nil { + return "", err + } + realizeService := realizestate.InitializeRealizeState(service.Service) + if err = realizeService.CheckRealizeState(retry.DefaultRetry, *nsxSubnet.Path, "RealizedLogicalSwitch"); err != nil { + log.Error(err, "failed to check subnet realization state", "ID", *nsxSubnet.Id) + return "", err + } + if err = service.SubnetStore.Apply(nsxSubnet); err != nil { + log.Error(err, "failed to add subnet to store", "ID", *nsxSubnet.Id) + return "", err + } + if subnetSet, ok := obj.(*v1alpha1.SubnetSet); ok { + if err = service.UpdateSubnetSetStatus(subnetSet); err != nil { + return "", err + } + } + log.Info("successfully updated nsxSubnet", "nsxSubnet", nsxSubnet) + return *nsxSubnet.Path, nil +} + +func (service *SubnetService) DeleteSubnet(nsxSubnet model.VpcSubnet) error { + vpcInfo, _ := common.ParseVPCResourcePath(*nsxSubnet.Path) + nsxSubnet.MarkedForDelete = &MarkedForDelete + // WrapHighLevelSubnet will modify the input subnet, make a copy for the following store update. + subnetCopy := nsxSubnet + orgRoot, err := service.WrapHierarchySubnet(&nsxSubnet, &vpcInfo) + if err != nil { + return err + } + if err = service.NSXClient.OrgRootClient.Patch(*orgRoot, &EnforceRevisionCheckParam); err != nil { + // Subnets that are not deleted successfully will finally be deleted by GC. + log.Error(err, "failed to delete Subnet", "ID", *nsxSubnet.Id) + return err + } + if err = service.SubnetStore.Apply(&subnetCopy); err != nil { + log.Error(err, "failed to delete Subnet from store", "ID", *nsxSubnet.Id) + return err + } + log.Info("successfully deleted nsxSubnet", "nsxSubnet", nsxSubnet) + return nil +} + +func (service *SubnetService) ListSubnetCreatedByCR() []model.VpcSubnet { + return service.listSubnetByCRType("subnet") +} + +func (service *SubnetService) listSubnetByCRType(crType string) []model.VpcSubnet { + subnets := service.SubnetStore.GetByIndex(common.TagScopeSubnetCRType, crType) + subnetList := []model.VpcSubnet{} + for _, subnet := range subnets { + subnetList = append(subnetList, subnet) + } + return subnetList +} + +func (service *SubnetService) ListSubnetCreatedBySubnetSet() []model.VpcSubnet { + return service.listSubnetByCRType("subnetset") +} + +func (service *SubnetService) ListSubnetSetID(ctx context.Context) sets.String { + crdSubnetSetList := &v1alpha1.SubnetSetList{} + subnetsetIDs := sets.NewString() + err := service.Client.List(ctx, crdSubnetSetList) + if err != nil { + log.Error(err, "failed to list subnetset CR") + return subnetsetIDs + } + for _, subnetset := range crdSubnetSetList.Items { + subnetsetIDs.Insert(string(subnetset.UID)) + } + return subnetsetIDs +} + +// check if subnet belongs to a subnetset, if yes, check if that subnetset still exists +func (service *SubnetService) IsOrphanSubnet(subnet model.VpcSubnet, subnetsetIDs sets.String) bool { + for _, tag := range subnet.Tags { + if *tag.Scope == common.TagScopeSubnetSetCRUID && subnetsetIDs.Has(*tag.Tag) { + return false + } + } + return true +} + +func (service *SubnetService) DeleteIPAllocation(orgID, projectID, vpcID, subnetID string) error { + ipAllocations, err := service.NSXClient.IPAllocationClient.List(orgID, projectID, vpcID, subnetID, ipPoolID, + nil, nil, nil, nil, nil, nil) + if err != nil { + log.Error(err, "failed to get ip-allocations", "Subnet", subnetID) + return err + } + for _, alloc := range ipAllocations.Results { + if err = service.NSXClient.IPAllocationClient.Delete(orgID, projectID, vpcID, subnetID, ipPoolID, *alloc.Id); err != nil { + log.Error(err, "failed to delete ip-allocation", "Subnet", subnetID, "ip-alloc", *alloc.Id) + return err + } + } + log.Info("all IP allocations have been deleted", "Subnet", subnetID) + return nil +} + +func (service *SubnetService) GetSubnetStatus(subnet *model.VpcSubnet) ([]model.VpcSubnetStatus, error) { + param, err := common.ParseVPCResourcePath(*subnet.Path) + if err != nil { + return nil, err + } + statusList, err := service.NSXClient.SubnetStatusClient.List(param.OrgID, param.ProjectID, param.VPCID, *subnet.Id) + if err != nil { + log.Error(err, "failed to get subnet status") + return nil, err + } + if len(statusList.Results) == 0 { + err := errors.New("empty status result") + log.Error(err, "no subnet status found") + return nil, err + } + return statusList.Results, nil +} + +func (service *SubnetService) getIPPoolUsage(nsxSubnet *model.VpcSubnet) (*model.PolicyPoolUsage, error) { + param, err := common.ParseVPCResourcePath(*nsxSubnet.Path) + if err != nil { + return nil, err + } + ipPool, err := service.NSXClient.IPPoolClient.Get(param.OrgID, param.ProjectID, param.VPCID, *nsxSubnet.Id, ipPoolID) + if err != nil { + log.Error(err, "failed to get ip-pool", "Subnet", *nsxSubnet.Id) + return nil, err + } + return ipPool.PoolUsage, nil +} + +func (service *SubnetService) GetIPPoolUsage(subnet *v1alpha1.Subnet) (*model.PolicyPoolUsage, error) { + nsxSubnets := service.SubnetStore.GetByIndex(common.TagScopeSubnetCRUID, string(subnet.GetUID())) + if len(nsxSubnets) == 0 { + return nil, errors.New("NSX Subnet doesn't exist in store") + } + return service.getIPPoolUsage(&nsxSubnets[0]) +} + +func (service *SubnetService) UpdateSubnetSetStatus(obj *v1alpha1.SubnetSet) error { + var subnetInfoList []v1alpha1.SubnetInfo + nsxSubnets := service.SubnetStore.GetByIndex(common.TagScopeSubnetSetCRUID, string(obj.GetUID())) + for _, subnet := range nsxSubnets { + statusList, err := service.GetSubnetStatus(&subnet) + if err != nil { + return err + } + subnetInfo := v1alpha1.SubnetInfo{ + NSXResourcePath: *subnet.Path, + } + for _, status := range statusList { + subnetInfo.IPAddresses = append(subnetInfo.IPAddresses, *status.NetworkAddress) + } + subnetInfoList = append(subnetInfoList, subnetInfo) + } + obj.Status.Subnets = subnetInfoList + if err := service.Client.Status().Update(context.Background(), obj); err != nil { + log.Error(err, "failed to update SubnetSet status") + return err + } + return nil +} + +func (service *SubnetService) ListSubnetID() sets.String { + subnets := service.SubnetStore.ListIndexFuncValues(common.TagScopeSubnetCRUID) + subnetSets := service.SubnetStore.ListIndexFuncValues(common.TagScopeSubnetSetCRUID) + return subnets.Union(subnetSets) +} + +func (service *SubnetService) Cleanup() error { + uids := service.ListSubnetID() + log.Info("cleaning up subnet", "count", len(uids)) + for uid := range uids { + nsxSubnets := service.SubnetStore.GetByIndex(common.TagScopeSubnetCRUID, string(uid)) + for _, nsxSubnet := range nsxSubnets { + err := service.DeleteSubnet(nsxSubnet) + if err != nil { + return err + } + } + } + return nil +} + +func (service *SubnetService) GenerateSubnetNSTags(obj client.Object, nsUID string) []model.Tag { + var tags []model.Tag + switch o := obj.(type) { + case *v1alpha1.Subnet: + tags = append(tags, + model.Tag{Scope: String(common.TagScopeVMNamespaceUID), Tag: String(nsUID)}, + model.Tag{Scope: String(common.TagScopeVMNamespace), Tag: String(obj.GetNamespace())}) + case *v1alpha1.SubnetSet: + findLabelDefaultPodSubnetSet := false + for k, v := range o.Labels { + if k == common.LabelDefaultSubnetSet && v == common.LabelDefaultPodSubnetSet { + findLabelDefaultPodSubnetSet = true + break + } + } + if findLabelDefaultPodSubnetSet { + tags = append(tags, + model.Tag{Scope: common.String(common.TagScopeNamespaceUID), Tag: common.String(nsUID)}, + model.Tag{Scope: common.String(common.TagScopeNamespace), Tag: common.String(obj.GetNamespace())}) + } else { + tags = append(tags, + model.Tag{Scope: common.String(common.TagScopeVMNamespaceUID), Tag: common.String(nsUID)}, + model.Tag{Scope: common.String(common.TagScopeVMNamespace), Tag: common.String(obj.GetNamespace())}) + } + } + return tags +} + +func (service *SubnetService) UpdateSubnetSetTags(ns string, vpcSubnets []model.VpcSubnet, tags []model.Tag) error { + for _, existingSubnet := range vpcSubnets { + subnetSet := &v1alpha1.SubnetSet{} + var name string + + matchNamespace := false + for _, tag := range existingSubnet.Tags { + if *tag.Scope == common.TagScopeSubnetSetCRName { + name = *tag.Tag + } + if *tag.Scope == common.TagScopeNamespace || *tag.Scope == common.TagScopeVMNamespace { + if *tag.Tag != ns { + break + } + matchNamespace = true + } + } + + if matchNamespace { + if err := service.Client.Get(context.Background(), types.NamespacedName{Namespace: ns, Name: name}, subnetSet); err != nil { + return err + } + newTags := append(service.buildBasicTags(subnetSet), tags...) + changed := common.CompareResource(SubnetToComparable(&existingSubnet), SubnetToComparable(&model.VpcSubnet{Tags: newTags})) + if !changed { + log.Info("NSX subnet tags unchanged, skip updating") + continue + } + existingSubnet.Tags = newTags + vpcInfo, err := common.ParseVPCResourcePath(*existingSubnet.Path) + if err != nil { + err := fmt.Errorf("failed to parse NSX VPC path for Subnet %s: %s", *existingSubnet.Path, err) + return err + } + if _, err := service.createOrUpdateSubnet(subnetSet, &existingSubnet, &vpcInfo); err != nil { + return err + } + log.Info("successfully updated subnet set tags", "subnetSet", subnetSet) + } + } + return nil +} diff --git a/pkg/nsx/services/subnet/wrap.go b/pkg/nsx/services/subnet/wrap.go new file mode 100644 index 000000000..94767ae0b --- /dev/null +++ b/pkg/nsx/services/subnet/wrap.go @@ -0,0 +1,145 @@ +package subnet + +import ( + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +// Patch API at infra level can be used in two flavours. +// 1. Like a regular API to update Infra object. +// 2. Hierarchical API: To create/update/delete entire or part of intent hierarchy Hierarchical. +// We use infra patch API in hierarchical mode to create/update/delete entire or part of intent hierarchy, +// for this convenience we can no longer CRUD CR separately, and reduce the number of API calls to NSX-T. + +// WrapHierarchySubnet Wrap the subnet for InfraClient to patch. +func (service *SubnetService) WrapHierarchySubnet(subnet *model.VpcSubnet, vpcInfo *common.VPCResourceInfo) (*model.OrgRoot, error) { + if orgRoot, err := service.wrapOrgRoot(subnet, vpcInfo.OrgID, vpcInfo.ProjectID, vpcInfo.VPCID); err != nil { + return nil, err + } else { + return orgRoot, nil + } +} + +func (service *SubnetService) wrapOrgRoot(subnet *model.VpcSubnet, orgID, projectID, vpcID string) (*model.OrgRoot, error) { + // This is the outermost layer of the hierarchy subnet. + // It doesn't need ID field. + resourceType := "OrgRoot" + children, err := service.wrapOrg(subnet, orgID, projectID, vpcID) + if err != nil { + return nil, err + } + orgRoot := model.OrgRoot{ + Children: children, + ResourceType: &resourceType, + } + return &orgRoot, nil +} + +func (service *SubnetService) wrapOrg(subnet *model.VpcSubnet, orgID, projectID, vpcID string) ([]*data.StructValue, error) { + children, err := service.wrapProject(subnet, projectID, vpcID) + if err != nil { + return nil, err + } + targetType := "Org" + childProject := model.ChildResourceReference{ + Id: &orgID, + ResourceType: "ChildResourceReference", + TargetType: &targetType, + Children: children, + } + dataValue, errors := NewConverter().ConvertToVapi(childProject, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SubnetService) wrapProject(subnet *model.VpcSubnet, projectID, vpcID string) ([]*data.StructValue, error) { + children, err := service.wrapVPC(subnet, vpcID) + if err != nil { + return nil, err + } + targetType := "Project" + childProject := model.ChildResourceReference{ + Id: &projectID, + ResourceType: "ChildResourceReference", + TargetType: &targetType, + Children: children, + } + dataValue, errors := NewConverter().ConvertToVapi(childProject, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SubnetService) wrapVPC(subnet *model.VpcSubnet, vpcID string) ([]*data.StructValue, error) { + children, err := service.wrapSubnet(subnet) + if err != nil { + return nil, err + } + targetType := "Vpc" + childVPC := model.ChildResourceReference{ + Id: &vpcID, + ResourceType: "ChildResourceReference", + TargetType: &targetType, + Children: children, + } + dataValue, errors := NewConverter().ConvertToVapi(childVPC, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +func (service *SubnetService) wrapSubnet(subnet *model.VpcSubnet) ([]*data.StructValue, error) { + subnet.ResourceType = &common.ResourceTypeSubnet + childSubnet := model.ChildVpcSubnet{ + Id: subnet.Id, + MarkedForDelete: subnet.MarkedForDelete, + ResourceType: "ChildVpcSubnet", + VpcSubnet: subnet, + } + dataValue, errors := NewConverter().ConvertToVapi(childSubnet, model.ChildVpcSubnetBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} + +// wrapDHCPStaticBindingConfig wraps DHCPStaticBindingConfig as children of VPCSubnet. +func (service *SubnetService) wrapDHCPStaticBindingConfig(config model.DhcpStaticBindingConfig) ([]*data.StructValue, error) { + configValue, errors := NewConverter().ConvertToVapi(config, model.ChildDhcpStaticBindingConfigBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + configChild := model.ChildDhcpStaticBindingConfig{ + ResourceType: "ChildDhcpStaticBindingConfig", + Id: config.Id, + DhcpStaticBindingConfig: configValue.(*data.StructValue), + MarkedForDelete: config.MarkedForDelete, + } + configChildValue, errors := NewConverter().ConvertToVapi(configChild, model.ChildDhcpStaticBindingConfigBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{configChildValue.(*data.StructValue)}, nil +} + +// wrapSegmentPort wraps SegmentPort as children of VPCSubnet. +// A reserved function for creating SegmentPorts, currently not used. +func (service *SubnetService) wrapSegmentPort(port model.SegmentPort) ([]*data.StructValue, error) { + portChild := model.ChildSegmentPort{ + ResourceType: "ChildSegmentPort", + Id: port.Id, + SegmentPort: &port, + MarkedForDelete: port.MarkedForDelete, + } + dataValue, errors := NewConverter().ConvertToVapi(portChild, model.ChildSegmentBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + return []*data.StructValue{dataValue.(*data.StructValue)}, nil +} diff --git a/pkg/nsx/services/subnet/wrap_test.go b/pkg/nsx/services/subnet/wrap_test.go new file mode 100644 index 000000000..f47e08970 --- /dev/null +++ b/pkg/nsx/services/subnet/wrap_test.go @@ -0,0 +1,76 @@ +package subnet + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/config" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/ratelimiter" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +func fakeService() *SubnetService { + c := nsx.NewConfig("localhost", "1", "1", []string{}, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, nil, nil, []string{}) + cluster, _ := nsx.NewCluster(c) + rc, _ := cluster.NewRestConnector() + service := &SubnetService{ + Service: common.Service{ + NSXClient: &nsx.Client{ + QueryClient: &fakeQueryClient{}, + RestConnector: rc, + NsxConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + NSXConfig: &config.NSXOperatorConfig{ + CoeConfig: &config.CoeConfig{ + Cluster: "k8scl-one:test", + }, + }, + }, + } + return service +} + +func TestSubnetService_wrapSubnet(t *testing.T) { + Converter := bindings.NewTypeConverter() + Converter.SetMode(bindings.REST) + service := fakeService() + mId, mTag, mScope := "11111", "11111", "nsx-op/subnet_cr_uid" + markDelete := true + s := model.VpcSubnet{ + Id: &mId, + Tags: []model.Tag{{Tag: &mTag, Scope: &mScope}}, + MarkedForDelete: &markDelete, + } + type args struct { + subnet *model.VpcSubnet + } + tests := []struct { + name string + args args + want []*data.StructValue + wantErr assert.ErrorAssertionFunc + }{ + {"1", args{&s}, nil, assert.Error}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, _ := service.wrapSubnet(tt.args.subnet) + for _, v := range got { + subnet, _ := Converter.ConvertToGolang(v, model.ChildVpcSubnetBindingType()) + child := subnet.(model.ChildVpcSubnet) + assert.Equal(t, mId, *child.Id) + assert.Equal(t, MarkedForDelete, *child.MarkedForDelete) + assert.NotNil(t, child.VpcSubnet) + } + }) + } +} diff --git a/pkg/nsx/services/subnetport/builder.go b/pkg/nsx/services/subnetport/builder.go new file mode 100644 index 000000000..b964a951c --- /dev/null +++ b/pkg/nsx/services/subnetport/builder.go @@ -0,0 +1,84 @@ +package subnetport + +import ( + "bytes" + "context" + "fmt" + + "github.com/google/uuid" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + String = common.String +) + +func (service *SubnetPortService) buildSubnetPort(obj interface{}, nsxSubnetPath string, contextID string, labelTags *map[string]string) (*model.SegmentPort, error) { + var objName, objNamespace, uid, appId string + switch o := obj.(type) { + case *v1alpha1.SubnetPort: + objName = o.Name + objNamespace = o.Namespace + uid = string(o.UID) + case *corev1.Pod: + objName = o.Name + objNamespace = o.Namespace + uid = string(o.UID) + appId = string(o.UID) + } + allocateAddresses := "BOTH" + nsxSubnetPortName := util.GenerateDisplayName(objName, "port", "", "", "") + nsxSubnetPortID := util.GenerateID(uid, "", "", "") + // use the subnetPort CR UID as the attachment uid generation to ensure the latter stable + nsxCIFID, err := uuid.NewRandomFromReader(bytes.NewReader([]byte(nsxSubnetPortID))) + if err != nil { + return nil, err + } + nsxSubnetPortPath := fmt.Sprintf("%s/ports/%s", nsxSubnetPath, uid) + if err != nil { + return nil, err + } + namespace := &corev1.Namespace{} + namespacedName := types.NamespacedName{ + Name: objNamespace, + } + if err := service.Client.Get(context.Background(), namespacedName, namespace); err != nil { + return nil, err + } + namespace_uid := namespace.UID + tags := util.BuildBasicTags(getCluster(service), obj, namespace_uid) + if labelTags != nil { + for k, v := range *labelTags { + tags = append(tags, model.Tag{Scope: String(k), Tag: String(v)}) + } + } + nsxSubnetPort := &model.SegmentPort{ + DisplayName: String(nsxSubnetPortName), + Id: String(nsxSubnetPortID), + Attachment: &model.PortAttachment{ + AllocateAddresses: &allocateAddresses, + Id: String(nsxCIFID.String()), + TrafficTag: common.Int64(0), + Type_: String("STATIC"), + }, + Tags: tags, + Path: &nsxSubnetPortPath, + ParentPath: &nsxSubnetPath, + } + if appId != "" { + nsxSubnetPort.Attachment.AppId = &appId + nsxSubnetPort.Attachment.ContextId = &contextID + } + return nsxSubnetPort, nil +} + +func getCluster(service *SubnetPortService) string { + return service.NSXConfig.Cluster +} diff --git a/pkg/nsx/services/subnetport/compare.go b/pkg/nsx/services/subnetport/compare.go new file mode 100644 index 000000000..fde134657 --- /dev/null +++ b/pkg/nsx/services/subnetport/compare.go @@ -0,0 +1,49 @@ +package subnetport + +import ( + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +type ( + SubnetPort model.SegmentPort +) + +type Comparable = common.Comparable + +func (sp *SubnetPort) Key() string { + return *sp.Id +} + +func (sp *SubnetPort) Value() data.DataValue { + s := &SubnetPort{ + Id: sp.Id, + DisplayName: sp.DisplayName, + Tags: sp.Tags, + Attachment: sp.Attachment, + } + if sp.Attachment != nil { + // Ignoring the fields BmsInterfaceConfig, ContextType, EvpnVlans, HyperbusMode + // because operator doesn't set them. + s.Attachment = &model.PortAttachment{ + AllocateAddresses: sp.Attachment.AllocateAddresses, + AppId: sp.Attachment.AppId, + ContextId: sp.Attachment.ContextId, + Id: sp.Attachment.Id, + TrafficTag: sp.Attachment.TrafficTag, + Type_: sp.Attachment.AllocateAddresses, + } + } + dataValue, _ := ComparableToSubnetPort(s).GetDataValue__() + return dataValue +} + +func SubnetPortToComparable(sp *model.SegmentPort) Comparable { + return (*SubnetPort)(sp) +} + +func ComparableToSubnetPort(sp Comparable) *model.SegmentPort { + return (*model.SegmentPort)(sp.(*SubnetPort)) +} diff --git a/pkg/nsx/services/subnetport/store.go b/pkg/nsx/services/subnetport/store.go new file mode 100644 index 000000000..adf87ecf0 --- /dev/null +++ b/pkg/nsx/services/subnetport/store.go @@ -0,0 +1,132 @@ +package subnetport + +import ( + "errors" + + "k8s.io/apimachinery/pkg/types" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +// keyFunc is used to get the key of a resource, usually, which is the ID of the resource +func keyFunc(obj interface{}) (string, error) { + switch v := obj.(type) { + case model.SegmentPort: + return *v.Id, nil + case types.UID: + return string(v), nil + default: + return "", errors.New("keyFunc doesn't support unknown type") + } +} + +func filterTag(tags []model.Tag, tagScope string) []string { + var res []string + for _, tag := range tags { + if *tag.Scope == tagScope { + res = append(res, *tag.Tag) + } + } + return res +} + +// subnetPortIndexByCRUID is used to get index of a resource, usually, which is the UID of the CR controller reconciles, +// index is used to filter out resources which are related to the CR +func subnetPortIndexByCRUID(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.SegmentPort: + return filterTag(o.Tags, common.TagScopeSubnetPortCRUID), nil + default: + return nil, errors.New("subnetPortIndexByCRUID doesn't support unknown type") + } +} + +func subnetPortIndexByPodUID(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.SegmentPort: + return filterTag(o.Tags, common.TagScopePodUID), nil + default: + return nil, errors.New("subnetPortIndexByCRUID doesn't support unknown type") + } +} + +func subnetPortIndexBySubnetID(obj interface{}) ([]string, error) { + switch o := obj.(type) { + case model.SegmentPort: + vpcInfo, err := common.ParseVPCResourcePath(*o.Path) + if err != nil { + return nil, err + } + return []string{vpcInfo.ParentID}, nil + + default: + return nil, errors.New("subnetPortIndexBySubnetID doesn't support unknown type") + } +} + +// SubnetPortStore is a store for SubnetPorts +type SubnetPortStore struct { + common.ResourceStore +} + +func (vs *SubnetPortStore) Apply(i interface{}) error { + if i == nil { + return nil + } + subnetPort := i.(*model.SegmentPort) + if subnetPort.MarkedForDelete != nil && *subnetPort.MarkedForDelete { + err := vs.Delete(*subnetPort) + log.V(1).Info("delete SubnetPort from store", "subnetport", subnetPort) + if err != nil { + return err + } + } else { + err := vs.Add(*subnetPort) + log.V(1).Info("add SubnetPort to store", "subnetport", subnetPort) + if err != nil { + return err + } + } + return nil +} + +func (subnetPortStore *SubnetPortStore) GetByKey(key string) *model.SegmentPort { + var subnetPort model.SegmentPort + obj := subnetPortStore.ResourceStore.GetByKey(key) + if obj != nil { + subnetPort = obj.(model.SegmentPort) + } + return &subnetPort +} + +func (subnetPortStore *SubnetPortStore) GetByIndex(key string, value string) []model.SegmentPort { + segmentPorts := make([]model.SegmentPort, 0) + objs := subnetPortStore.ResourceStore.GetByIndex(key, value) + for _, subnetPort := range objs { + segmentPorts = append(segmentPorts, subnetPort.(model.SegmentPort)) + } + return segmentPorts +} + +func (vs *SubnetPortStore) GetSubnetPortsByNamespace(ns string) []model.SegmentPort { + var ret []model.SegmentPort + subnetPorts := vs.List() + if len(subnetPorts) == 0 { + log.V(1).Info("No subnet port found in SubnetPort store") + return ret + } + + for _, subnetPort := range subnetPorts { + msubnetport := subnetPort.(model.SegmentPort) + tags := msubnetport.Tags + for _, tag := range tags { + // TODO: consider to create index for common.TagScopeNamespace like common.TagScopeSubnetPortCRUID, and leverage functions like getByIndex to perform searches. + if *tag.Scope == common.TagScopeNamespace && *tag.Tag == ns { + ret = append(ret, msubnetport) + } + } + } + return ret +} diff --git a/pkg/nsx/services/subnetport/subnetport.go b/pkg/nsx/services/subnetport/subnetport.go new file mode 100644 index 000000000..e44efeb63 --- /dev/null +++ b/pkg/nsx/services/subnetport/subnetport.go @@ -0,0 +1,269 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 */ + +package subnetport + +import ( + "errors" + "sync" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/logger" + servicecommon "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/realizestate" + nsxutil "github.com/vmware-tanzu/nsx-operator/pkg/nsx/util" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + log = logger.Log + ResourceTypeSubnetPort = servicecommon.ResourceTypeSubnetPort + MarkedForDelete = true +) + +type SubnetPortService struct { + servicecommon.Service + SubnetPortStore *SubnetPortStore +} + +// InitializeSubnetPort sync NSX resources. +func InitializeSubnetPort(service servicecommon.Service) (*SubnetPortService, error) { + wg := sync.WaitGroup{} + wgDone := make(chan bool) + fatalErrors := make(chan error) + + wg.Add(1) + + subnetPortService := &SubnetPortService{Service: service} + + subnetPortService.SubnetPortStore = &SubnetPortStore{ResourceStore: servicecommon.ResourceStore{ + Indexer: cache.NewIndexer( + keyFunc, + cache.Indexers{ + servicecommon.TagScopeSubnetPortCRUID: subnetPortIndexByCRUID, + servicecommon.TagScopePodUID: subnetPortIndexByPodUID, + servicecommon.IndexKeySubnetID: subnetPortIndexBySubnetID, + }), + BindingType: model.SegmentPortBindingType(), + }} + + go subnetPortService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeSubnetPort, nil, subnetPortService.SubnetPortStore) + + go func() { + wg.Wait() + close(wgDone) + }() + + select { + case <-wgDone: + break + case err := <-fatalErrors: + close(fatalErrors) + return subnetPortService, err + } + + return subnetPortService, nil +} + +func (service *SubnetPortService) CreateOrUpdateSubnetPort(obj interface{}, nsxSubnetPath string, contextID string, tags *map[string]string) (*model.SegmentPortState, error) { + var uid string + switch o := obj.(type) { + case *v1alpha1.SubnetPort: + uid = string(o.UID) + case *corev1.Pod: + uid = string(o.UID) + } + log.Info("creating or updating subnetport", "nsxSubnetPort.Id", uid, "nsxSubnetPath", nsxSubnetPath) + nsxSubnetPort, err := service.buildSubnetPort(obj, nsxSubnetPath, contextID, tags) + if err != nil { + log.Error(err, "failed to build NSX subnet port", "nsxSubnetPort.Id", uid, "nsxSubnetPath", nsxSubnetPath, "contextID", contextID) + return nil, err + } + subnetInfo, err := servicecommon.ParseVPCResourcePath(nsxSubnetPath) + if err != nil { + log.Error(err, "failed to parse NSX subnet path", "path", nsxSubnetPath) + return nil, err + } + existingSubnetPort := service.SubnetPortStore.GetByKey(*nsxSubnetPort.Id) + isChanged := servicecommon.CompareResource(SubnetPortToComparable(existingSubnetPort), SubnetPortToComparable(nsxSubnetPort)) + if !isChanged { + log.Info("NSX subnet port not changed, skipping the update", "nsxSubnetPort.Id", nsxSubnetPort.Id, "nsxSubnetPath", nsxSubnetPath) + // We don't need to update it but still need to check realized state. + } else { + log.Info("updating the NSX subnet port", "existingSubnetPort", existingSubnetPort, "desiredSubnetPort", nsxSubnetPort) + err = service.NSXClient.PortClient.Patch(subnetInfo.OrgID, subnetInfo.ProjectID, subnetInfo.VPCID, subnetInfo.ID, *nsxSubnetPort.Id, *nsxSubnetPort) + if err != nil { + log.Error(err, "failed to create or update subnet port", "nsxSubnetPort.Id", *nsxSubnetPort.Id, "nsxSubnetPath", nsxSubnetPath) + return nil, err + } + err = service.SubnetPortStore.Apply(nsxSubnetPort) + if err != nil { + return nil, err + } + if existingSubnetPort != nil { + log.Info("updated NSX subnet port", "nsxSubnetPort.Path", *nsxSubnetPort.Path) + } else { + log.Info("created NSX subnet port", "nsxSubnetPort.Path", *nsxSubnetPort.Path) + } + } + nsxSubnetPortState, err := service.CheckSubnetPortState(obj, nsxSubnetPath) + if err != nil { + log.Error(err, "check and update NSX subnet port state failed, would retry exponentially", "nsxSubnetPort.Id", *nsxSubnetPort.Id, "nsxSubnetPath", nsxSubnetPath) + return nil, err + } + if isChanged { + log.Info("successfully created or updated subnetport", "nsxSubnetPort.Id", *nsxSubnetPort.Id) + } else { + log.Info("subnetport already existed", "subnetport", *nsxSubnetPort.Id) + } + return nsxSubnetPortState, nil +} + +// CheckSubnetPortState will check the port realized status then get the port state to prepare the CR status. +func (service *SubnetPortService) CheckSubnetPortState(obj interface{}, nsxSubnetPath string) (*model.SegmentPortState, error) { + var uid types.UID + switch o := obj.(type) { + case *v1alpha1.SubnetPort: + uid = o.UID + case *v1.Pod: + uid = o.UID + } + nsxSubnetPort := service.SubnetPortStore.GetByKey(string(uid)) + if nsxSubnetPort == nil { + return nil, errors.New("failed to get subnet port from store") + } + realizeService := realizestate.InitializeRealizeState(service.Service) + if err := realizeService.CheckRealizeState(retry.DefaultRetry, *nsxSubnetPort.Path, "RealizedLogicalPort"); err != nil { + log.Error(err, "failed to get realized status", "subnetport path", *nsxSubnetPort.Path) + if realizestate.IsRealizeStateError(err) { + log.Error(err, "the created subnet port is in error realization state, cleaning the resource", "subnetport", uid) + // only recreate subnet port on RealizationErrorStateError. + if err := service.DeleteSubnetPort(uid); err != nil { + log.Error(err, "cleanup error subnetport failed", "subnetport", uid) + return nil, err + } + } + return nil, err + } + // TODO: avoid to get subnetport state again if we already got it. + nsxPortState, err := service.GetSubnetPortState(obj, nsxSubnetPath) + if err != nil { + return nil, err + } + log.Info("got the NSX subnet port state", "nsxPortState.RealizedBindings", nsxPortState.RealizedBindings, "uid", uid) + if len(nsxPortState.RealizedBindings) == 0 { + return nsxPortState, errors.New("empty realized bindings") + } + return nsxPortState, nil +} + +func (service *SubnetPortService) GetSubnetPortState(obj interface{}, nsxSubnetPath string) (*model.SegmentPortState, error) { + var uid types.UID + switch o := obj.(type) { + case *v1alpha1.SubnetPort: + uid = o.UID + case *v1.Pod: + uid = o.UID + } + nsxOrgID, nsxProjectID, nsxVPCID, nsxSubnetID := nsxutil.ParseVPCPath(nsxSubnetPath) + nsxSubnetPortState, err := service.NSXClient.PortStateClient.Get(nsxOrgID, nsxProjectID, nsxVPCID, nsxSubnetID, string(uid), nil, nil) + if err != nil { + log.Error(err, "failed to get subnet port state", "nsxSubnetPortID", uid, "nsxSubnetPath", nsxSubnetPath) + return nil, err + } + return &nsxSubnetPortState, nil +} + +func (service *SubnetPortService) DeleteSubnetPort(uid types.UID) error { + nsxSubnetPort := service.SubnetPortStore.GetByKey(string(uid)) + if nsxSubnetPort.Id == nil { + log.Info("NSX subnet port is not found in store, skip deleting it", "uid", uid) + return nil + } + nsxOrgID, nsxProjectID, nsxVPCID, nsxSubnetID := nsxutil.ParseVPCPath(*nsxSubnetPort.Path) + err := service.NSXClient.PortClient.Delete(nsxOrgID, nsxProjectID, nsxVPCID, nsxSubnetID, string(uid)) + if err != nil { + log.Error(err, "failed to delete subnetport", "nsxSubnetPort.Path", *nsxSubnetPort.Path) + return err + } + if err = service.SubnetPortStore.Delete(uid); err != nil { + return err + } + log.Info("successfully deleted nsxSubnetPort", "nsxSubnetPortID", uid) + return nil +} + +func (service *SubnetPortService) ListNSXSubnetPortIDForCR() sets.String { + log.V(2).Info("listing subnet port CR UIDs") + subnetPortSet := service.SubnetPortStore.ListIndexFuncValues(servicecommon.TagScopeSubnetPortCRUID) + return subnetPortSet +} + +func (service *SubnetPortService) ListNSXSubnetPortIDForPod() sets.String { + log.V(2).Info("listing pod UIDs") + subnetPortSet := service.SubnetPortStore.ListIndexFuncValues(servicecommon.TagScopePodUID) + return subnetPortSet +} + +func (service *SubnetPortService) GetGatewayNetmaskForSubnetPort(obj *v1alpha1.SubnetPort, nsxSubnetPath string) (string, string, error) { + // TODO: merge the logic to subnet service when subnet implementation is done. + subnetInfo, err := servicecommon.ParseVPCResourcePath(nsxSubnetPath) + if err != nil { + return "", "", err + } + // TODO: if the port is not the first on the same subnet, try to get the info from existing realized subnetport CR to avoid query NSX API again. + statusList, err := service.NSXClient.SubnetStatusClient.List(subnetInfo.OrgID, subnetInfo.ProjectID, subnetInfo.VPCID, subnetInfo.ID) + if err != nil { + log.Error(err, "failed to get subnet status") + return "", "", err + } + if len(statusList.Results) == 0 { + err := errors.New("empty status result") + log.Error(err, "no subnet status found") + return "", "", err + } + status := statusList.Results[0] + gateway, err := util.RemoveIPPrefix(*status.GatewayAddress) + if err != nil { + return "", "", err + } + prefix, err := util.GetIPPrefix(*status.GatewayAddress) + if err != nil { + return "", "", err + } + mask, err := util.GetSubnetMask(prefix) + if err != nil { + return "", "", err + } + return gateway, mask, nil +} + +func (service *SubnetPortService) GetSubnetPathForSubnetPortFromStore(nsxSubnetPortID string) string { + existingSubnetPort := service.SubnetPortStore.GetByKey(nsxSubnetPortID) + if existingSubnetPort.ParentPath == nil { + return "" + } + return *existingSubnetPort.ParentPath +} + +func (service *SubnetPortService) Cleanup() error { + subnetPorts := service.SubnetPortStore.List() + log.Info("cleanup subnetports", "count", len(subnetPorts)) + for _, subnetPort := range subnetPorts { + subnetPortID := types.UID(*subnetPort.(model.SegmentPort).Id) + err := service.DeleteSubnetPort(subnetPortID) + if err != nil { + log.Error(err, "cleanup subnetport failed", "subnetPortID", subnetPortID) + return err + } + } + return nil +} diff --git a/pkg/nsx/services/vpc/builder.go b/pkg/nsx/services/vpc/builder.go new file mode 100644 index 000000000..cd1621f13 --- /dev/null +++ b/pkg/nsx/services/vpc/builder.go @@ -0,0 +1,86 @@ +package vpc + +import ( + "net/netip" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/util" +) + +var ( + DefaultVPCIPAddressType = "IPV4" + DefaultLoadBalancerVPCEndpointEnabled = true +) + +// private ip block cidr is not unique, there maybe different ip blocks using same cidr, but for different vpc cr +// using cidr_vpccruid as key so that it could quickly check if ipblocks already created. +func generateIPBlockKey(block model.IpAddressBlock) string { + cidr := block.Cidr + vpc_uid := "" + for _, tag := range block.Tags { + if *tag.Scope == common.TagScopeVPCCRUID { + vpc_uid = *tag.Tag + } + } + return *cidr + "_" + vpc_uid +} + +func generateIPBlockSearchKey(cidr string, vpcCRUID string) string { + return cidr + "_" + vpcCRUID +} + +func buildPrivateIpBlock(vpc *v1alpha1.VPC, cidr, ip, project, cluster string) model.IpAddressBlock { + suffix := vpc.GetNamespace() + "-" + vpc.Name + "-" + ip + addr, _ := netip.ParseAddr(ip) + ipType := util.If(addr.Is4(), model.IpAddressBlock_IP_ADDRESS_TYPE_IPV4, model.IpAddressBlock_IP_ADDRESS_TYPE_IPV6).(string) + blockType := model.IpAddressBlock_VISIBILITY_PRIVATE + block := model.IpAddressBlock{ + DisplayName: common.String(util.GenerateDisplayName("ipblock", "", suffix, "", cluster)), + Id: common.String(string(vpc.UID) + "_" + ip), + Tags: util.BuildBasicTags(cluster, vpc, ""), // ipblock and vpc can use the same tags + Cidr: &cidr, + IpAddressType: &ipType, + Visibility: &blockType, + } + + return block +} + +func buildNSXVPC(obj *v1alpha1.VPC, nc VPCNetworkConfigInfo, cluster string, pathMap map[string]string, nsxVPC *model.Vpc) (*model.Vpc, error) { + vpc := &model.Vpc{} + if nsxVPC != nil { + // for upgrade case, only check public/private ip block size changing + if !IsVPCChanged(nc, nsxVPC) { + log.Info("no changes on current NSX VPC, skip updating", "VPC", nsxVPC.Id) + return nil, nil + } + // for updating vpc case, use current vpc id, name + vpc = nsxVPC + } else { + // for creating vpc case, fill in vpc properties based on networkconfig + suffix := obj.GetNamespace() + "-" + obj.Name + vpcName := util.GenerateDisplayName("vpc", "", suffix, "", cluster) + vpc.DisplayName = &vpcName + vpc.Id = common.String(string(obj.GetUID())) + vpc.DefaultGatewayPath = &nc.DefaultGatewayPath + vpc.IpAddressType = &DefaultVPCIPAddressType + + siteInfos := []model.SiteInfo{ + { + EdgeClusterPaths: []string{nc.EdgeClusterPath}, + }, + } + vpc.SiteInfos = siteInfos + vpc.LoadBalancerVpcEndpoint = &model.LoadBalancerVPCEndpoint{Enabled: &DefaultLoadBalancerVPCEndpointEnabled} + vpc.Tags = util.BuildBasicTags(cluster, obj, "") + } + + // update private/public blocks + vpc.ExternalIpv4Blocks = nc.ExternalIPv4Blocks + vpc.PrivateIpv4Blocks = util.GetMapValues(pathMap) + + return vpc, nil +} diff --git a/pkg/nsx/services/vpc/compare.go b/pkg/nsx/services/vpc/compare.go new file mode 100644 index 000000000..aacbccea7 --- /dev/null +++ b/pkg/nsx/services/vpc/compare.go @@ -0,0 +1,19 @@ +package vpc + +import ( + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +// currently we only support appending public/private cidrs +// so only comparing list size is enough to identify if vcp changed +func IsVPCChanged(nc VPCNetworkConfigInfo, vpc *model.Vpc) bool { + if len(nc.ExternalIPv4Blocks) != len(vpc.ExternalIpv4Blocks) { + return true + } + + if len(nc.PrivateIPv4CIDRs) != len(vpc.PrivateIpv4Blocks) { + return true + } + + return false +} diff --git a/pkg/nsx/services/vpc/store.go b/pkg/nsx/services/vpc/store.go index b5b96e58c..37cdc443f 100644 --- a/pkg/nsx/services/vpc/store.go +++ b/pkg/nsx/services/vpc/store.go @@ -13,6 +13,8 @@ func keyFunc(obj interface{}) (string, error) { switch v := obj.(type) { case model.Vpc: return *v.Id, nil + case model.IpAddressBlock: + return generateIPBlockKey(obj.(model.IpAddressBlock)), nil default: return "", errors.New("keyFunc doesn't support unknown type") } @@ -25,11 +27,25 @@ func indexFunc(obj interface{}) ([]string, error) { switch o := obj.(type) { case model.Vpc: return filterTag(o.Tags), nil + case model.IpAddressBlock: + return filterTag(o.Tags), nil default: return res, errors.New("indexFunc doesn't support unknown type") } } +// for ip block, one vpc may contains multiple ipblock with same vpc cr id +// add one more indexer using path +func indexPathFunc(obj interface{}) ([]string, error) { + res := make([]string, 0, 5) + switch o := obj.(type) { + case model.IpAddressBlock: + return append(res, *o.Path), nil + default: + return res, errors.New("indexPathFunc doesn't support unknown type") + } +} + var filterTag = func(v []model.Tag) []string { res := make([]string, 0, 5) for _, tag := range v { @@ -40,12 +56,38 @@ var filterTag = func(v []model.Tag) []string { return res } +// IPBlockStore is a store for private ip blocks +type IPBlockStore struct { + common.ResourceStore +} + +func (is *IPBlockStore) Apply(i interface{}) error { + if i == nil { + return nil + } + ipblock := i.(*model.IpAddressBlock) + if ipblock.MarkedForDelete != nil && *ipblock.MarkedForDelete { + err := is.Delete(*ipblock) + log.V(1).Info("delete ipblock from store", "IPBlock", ipblock) + if err != nil { + return err + } + } else { + err := is.Add(*ipblock) + log.V(1).Info("add IPBlock to store", "IPBlock", ipblock) + if err != nil { + return err + } + } + return nil +} + // VPCStore is a store for VPCs type VPCStore struct { common.ResourceStore } -func (vs *VPCStore) Operate(i interface{}) error { +func (vs *VPCStore) Apply(i interface{}) error { if i == nil { return nil } @@ -85,3 +127,23 @@ func (vs *VPCStore) GetVPCsByNamespace(ns string) []model.Vpc { } return ret } + +func (vs *VPCStore) GetByKey(key string) *model.Vpc { + obj := vs.ResourceStore.GetByKey(key) + if obj != nil { + vpc := obj.(model.Vpc) + return &vpc + } + return nil +} + +func (is *IPBlockStore) GetByIndex(index string, value string) *model.IpAddressBlock { + indexResults, err := is.ResourceStore.Indexer.ByIndex(index, value) + if err != nil || len(indexResults) == 0 { + log.Error(err, "failed to get obj by index", "index", value) + return nil + } + + block := indexResults[0].((model.IpAddressBlock)) + return &block +} diff --git a/pkg/nsx/services/vpc/store_test.go b/pkg/nsx/services/vpc/store_test.go index aff1c9520..be84dfe37 100644 --- a/pkg/nsx/services/vpc/store_test.go +++ b/pkg/nsx/services/vpc/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" "github.com/vmware/vsphere-automation-sdk-go/runtime/data" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" "github.com/vmware-tanzu/nsx-operator/pkg/config" @@ -130,7 +131,9 @@ func Test_InitializeVPCStore(t *testing.T) { }) defer patches2.Reset() - service.InitializeResourceStore(&wg, fatalErrors, ResourceTypeVPC, vpcStore) + service.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypeVpc, nil, vpcStore) + assert.Empty(t, fatalErrors) + assert.Equal(t, sets.String(sets.String{}), vpcStore.ListIndexFuncValues(common.TagScopeVPCCRUID)) } func TestVPCStore_CRUDResource(t *testing.T) { @@ -152,7 +155,7 @@ func TestVPCStore_CRUDResource(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.wantErr(t, vpcStore.Operate(tt.args.i), fmt.Sprintf("CRUDResource(%v)", tt.args.i)) + tt.wantErr(t, vpcStore.Apply(tt.args.i), fmt.Sprintf("CRUDResource(%v)", tt.args.i)) }) } } @@ -208,21 +211,21 @@ func TestVPCStore_CRUDResource_List(t *testing.T) { } vpc1 := model.Vpc{ - DisplayName: &vpcName1, - Id: &vpcID1, - Tags: tag1, - IpAddressType: &IPv4Type, - PrivateIpv4Blocks: []string{"1.1.1.0/24"}, - PublicIpv4Blocks: []string{"2.2.2.0/24"}, + DisplayName: &vpcName1, + Id: &vpcID1, + Tags: tag1, + IpAddressType: &IPv4Type, + PrivateIpv4Blocks: []string{"1.1.1.0/24"}, + ExternalIpv4Blocks: []string{"2.2.2.0/24"}, } vpc2 := model.Vpc{ - DisplayName: &vpcName2, - Id: &vpcID2, - Tags: tag2, - IpAddressType: &IPv4Type, - PrivateIpv4Blocks: []string{"3.3.3.0/24"}, - PublicIpv4Blocks: []string{"4.4.4.0/24"}, + DisplayName: &vpcName2, + Id: &vpcID2, + Tags: tag2, + IpAddressType: &IPv4Type, + PrivateIpv4Blocks: []string{"3.3.3.0/24"}, + ExternalIpv4Blocks: []string{"4.4.4.0/24"}, } tests := []struct { name string @@ -233,8 +236,8 @@ func TestVPCStore_CRUDResource_List(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - vpcStore.Operate(&vpc1) - vpcStore.Operate(&vpc2) + vpcStore.Apply(&vpc1) + vpcStore.Apply(&vpc2) got := vpcStore.List() if len(got) != 2 { t.Errorf("size = %v, want %v", len(got), 2) diff --git a/pkg/nsx/services/vpc/types.go b/pkg/nsx/services/vpc/types.go new file mode 100644 index 000000000..b4a9b6f20 --- /dev/null +++ b/pkg/nsx/services/vpc/types.go @@ -0,0 +1,13 @@ +package vpc + +type VPCNetworkConfigInfo struct { + Org string + Name string + DefaultGatewayPath string + EdgeClusterPath string + NsxtProject string + ExternalIPv4Blocks []string + PrivateIPv4CIDRs []string + DefaultIPv4SubnetSize int + DefaultSubnetAccessMode string +} diff --git a/pkg/nsx/services/vpc/vpc.go b/pkg/nsx/services/vpc/vpc.go index 2bf75072a..3befa7f8c 100644 --- a/pkg/nsx/services/vpc/vpc.go +++ b/pkg/nsx/services/vpc/vpc.go @@ -1,26 +1,103 @@ package vpc import ( + "context" + "errors" + "fmt" + "net" + "strings" "sync" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" "github.com/vmware-tanzu/nsx-operator/pkg/logger" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/realizestate" ) var ( log = logger.Log - ResourceTypeVPC = common.ResourceTypeVPC + ctx = context.Background() + ResourceTypeVPC = common.ResourceTypeVpc NewConverter = common.NewConverter + // The following variables are defined as interface, they should be initialized as concrete type - vpcStore common.Store + vpcStore common.Store + ipblockStore common.Store + + // this store contains mapping relation of network config name and network config entity + VPCNetworkConfigMap = map[string]VPCNetworkConfigInfo{} + + // this map contains mapping relation between namespace and the network config it uses. + VPCNSNetworkconfigMap = map[string]string{} + + resourceType = "resource_type" + EnforceRevisionCheckParam = false + MarkedForDelete = true ) type VPCService struct { common.Service - vpcStore *VPCStore + VpcStore *VPCStore + IpblockStore *IPBlockStore +} + +func (s *VPCService) RegisterVPCNetworkConfig(ncCRName string, info VPCNetworkConfigInfo) { + VPCNetworkConfigMap[ncCRName] = info +} + +func (s *VPCService) UnregisterVPCNetworkConfig(ncCRName string) { + delete(VPCNetworkConfigMap, ncCRName) +} + +func (s *VPCService) GetVPCNetworkConfig(ncCRName string) (VPCNetworkConfigInfo, bool) { + nc, exist := VPCNetworkConfigMap[ncCRName] + return nc, exist +} + +func (s *VPCService) RegisterNamespaceNetworkconfigBinding(ns string, ncCRName string) { + VPCNSNetworkconfigMap[ns] = ncCRName +} + +func (s *VPCService) UnRegisterNamespaceNetworkconfigBinding(ns string) { + delete(VPCNSNetworkconfigMap, ns) +} + +// find the namespace list which is using the given network configuration +func (s *VPCService) GetNamespacesByNetworkconfigName(nc string) []string { + result := []string{} + for key, value := range VPCNSNetworkconfigMap { + if value == nc { + result = append(result, key) + } + } + return result +} + +func (s *VPCService) GetVPCNetworkConfigByNamespace(ns string) *VPCNetworkConfigInfo { + ncName, nameExist := VPCNSNetworkconfigMap[ns] + if !nameExist { + log.Info("failed to get network config name for namespace", "Namespace", ns) + return nil + } + + nc, ncExist := s.GetVPCNetworkConfig(ncName) + if !ncExist { + log.Info("failed to get network config info using network config name", "Name", ncName) + return nil + } + return &nc +} + +// TBD: for now, if network config info do not contains private cidr, we consider this is +// incorrect configuration, and skip creating this VPC CR +func (s *VPCService) ValidateNetworkConfig(nc VPCNetworkConfigInfo) bool { + return nc.PrivateIPv4CIDRs != nil && len(nc.PrivateIPv4CIDRs) != 0 } // InitializeVPC sync NSX resources @@ -29,16 +106,25 @@ func InitializeVPC(service common.Service) (*VPCService, error) { wgDone := make(chan bool) fatalErrors := make(chan error) - wg.Add(1) + wg.Add(2) VPCService := &VPCService{Service: service} - VPCService.vpcStore = &VPCStore{ResourceStore: common.ResourceStore{ + VPCService.VpcStore = &VPCStore{ResourceStore: common.ResourceStore{ Indexer: cache.NewIndexer(keyFunc, cache.Indexers{common.TagScopeVPCCRUID: indexFunc}), BindingType: model.VpcBindingType(), }} - go VPCService.InitializeResourceStore(&wg, fatalErrors, ResourceTypeVPC, vpcStore) + VPCService.IpblockStore = &IPBlockStore{ResourceStore: common.ResourceStore{ + Indexer: cache.NewIndexer(keyFunc, cache.Indexers{ + common.TagScopeVPCCRUID: indexFunc, + common.IndexKeyPathPath: indexPathFunc}), + BindingType: model.IpAddressBlockBindingType(), + }} + + //initialize vpc store and ip blocks store + go VPCService.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypeVpc, nil, VPCService.VpcStore) + go VPCService.InitializeResourceStore(&wg, fatalErrors, common.ResourceTypeIPBlock, nil, VPCService.IpblockStore) go func() { wg.Wait() @@ -57,5 +143,329 @@ func InitializeVPC(service common.Service) (*VPCService, error) { } func (s *VPCService) GetVPCsByNamespace(namespace string) []model.Vpc { - return s.vpcStore.GetVPCsByNamespace(namespace) + return s.VpcStore.GetVPCsByNamespace(namespace) +} + +func (s *VPCService) ListVPC() []model.Vpc { + vpcs := s.VpcStore.List() + vpcSet := []model.Vpc{} + for _, vpc := range vpcs { + vpcSet = append(vpcSet, vpc.(model.Vpc)) + } + return vpcSet +} + +func (s *VPCService) DeleteVPC(path string) error { + pathInfo, err := common.ParseVPCResourcePath(path) + if err != nil { + return err + } + vpcClient := s.NSXClient.VPCClient + vpc := s.VpcStore.GetByKey(pathInfo.VPCID) + if vpc == nil { + return nil + } + + if err := vpcClient.Delete(pathInfo.OrgID, pathInfo.ProjectID, pathInfo.VPCID); err != nil { + return err + } + vpc.MarkedForDelete = &MarkedForDelete + if err := s.VpcStore.Apply(vpc); err != nil { + return err + } + + log.Info("successfully deleted NSX VPC", "VPC", pathInfo.VPCID) + return nil +} + +func (s *VPCService) deleteIPBlock(path string) error { + ipblockClient := s.NSXClient.IPBlockClient + parts := strings.Split(path, "/") + log.Info("deleting private ip block", "ORG", parts[2], "Project", parts[4], "ID", parts[7]) + if err := ipblockClient.Delete(parts[2], parts[4], parts[7]); err != nil { + log.Error(err, "failed to delete ip block", "Path", path) + return err + } + return nil +} + +func (s *VPCService) DeleteIPBlockInVPC(vpc model.Vpc) error { + blocks := vpc.PrivateIpv4Blocks + if blocks == nil || len(blocks) == 0 { + log.Info("no private cidr list, skip deleting private ip blocks") + return nil + } + + for _, block := range blocks { + if err := s.deleteIPBlock(block); err != nil { + return err + } + vpcCRUid := "" + for _, tag := range vpc.Tags { + if *tag.Scope == common.TagScopeVPCCRUID { + vpcCRUid = *tag.Tag + } + } + log.V(2).Info("search ip block from store using index and path", "index", common.TagScopeVPCCRUID, "Value", vpcCRUid, "Path", block) + // using index vpc cr id may get multiple ipblocks, add path to filter the correct one + ipblock := s.IpblockStore.GetByIndex(common.IndexKeyPathPath, block) + if ipblock != nil { + log.Info("deleting ip blocks", "IPBlock", ipblock) + ipblock.MarkedForDelete = &MarkedForDelete + s.IpblockStore.Apply(ipblock) + } + } + log.Info("successfully deleted all ip blocks") + return nil +} + +func (s *VPCService) CreatOrUpdatePrivateIPBlock(obj *v1alpha1.VPC, nc VPCNetworkConfigInfo) (map[string]string, error) { + // if network config contains PrivateIPV4CIDRs section, create private ip block for each cidr + path := map[string]string{} + if nc.PrivateIPv4CIDRs != nil { + for _, pCidr := range nc.PrivateIPv4CIDRs { + log.Info("start processing private cidr", "cidr", pCidr) + // if parse success, then check if private cidr exist, here we suppose it must be a cidr format string + ip, _, err := net.ParseCIDR(pCidr) + if err != nil { + message := fmt.Sprintf("invalid cidr %s for VPC %s", pCidr, obj.Name) + fmtError := errors.New(message) + log.Error(fmtError, message) + return nil, fmtError + } + // check if private ip block already exist + // use cidr_project_ns as search key + key := generateIPBlockSearchKey(pCidr, string(obj.UID)) + log.Info("using key to search from ipblock store", "Key", key) + block := s.IpblockStore.GetByKey(key) + if block == nil { + log.Info("no ip block found in store for cidr", "CIDR", pCidr) + block := buildPrivateIpBlock(obj, pCidr, ip.String(), nc.NsxtProject, s.NSXConfig.Cluster) + log.Info("creating ip block", "IPBlock", block.Id, "VPC", obj.Name) + // can not find private ip block from store, create one + _err := s.NSXClient.IPBlockClient.Patch(nc.Org, nc.NsxtProject, *block.Id, block) + if _err != nil { + message := fmt.Sprintf("failed to create private ip block for cidr %s for VPC %s", pCidr, obj.Name) + ipblockError := errors.New(message) + log.Error(ipblockError, message) + return nil, ipblockError + } + createdBlock, err := s.NSXClient.IPBlockClient.Get(nc.Org, nc.NsxtProject, *block.Id) + if err != nil { + // created by can not get, ignore this error + log.Info("failed to read ip blocks from NSX", "Project", nc.NsxtProject, "IPBlock", block.Id) + continue + } + // update ip block store + s.IpblockStore.Add(createdBlock) + path[pCidr] = *createdBlock.Path + } else { + eBlock := block.(model.IpAddressBlock) + path[pCidr] = *eBlock.Path + log.Info("ip block found in store for cidr using key", "CIDR", pCidr, "Key", key) + } + } + } + return path, nil +} + +func (s *VPCService) getNetworkconfigNameFromNS(ns string) (string, error) { + obj := &v1.Namespace{} + if err := s.Client.Get(ctx, types.NamespacedName{ + Name: ns, + Namespace: ns, + }, obj); err != nil { + log.Error(err, "failed to fetch namespace", "Namespace", ns) + return "", err + } + + annos := obj.Annotations + if annos == nil || len(annos) == 0 { + return common.DefaultNetworkConfigName, nil + } + + ncName, exist := annos[common.AnnotationVPCNetworkConfig] + if !exist { + return common.DefaultNetworkConfigName, nil + } + return ncName, nil +} + +func (s *VPCService) GetDefaultSNATIP(vpc model.Vpc) (string, error) { + ruleClient := s.NSXClient.NATRuleClient + info, err := common.ParseVPCResourcePath(*vpc.Path) + if err != nil { + log.Error(err, "failed to parse VPC path to get default SNAT ip", "Path", vpc.Path) + return "", err + } + var cursor *string = nil + // TODO: support scale scenario + pageSize := int64(1000) + markedForDelete := false + results, err := ruleClient.List(info.OrgID, info.ProjectID, info.VPCID, common.DefaultSNATID, cursor, &markedForDelete, nil, &pageSize, nil, nil) + if err != nil { + log.Error(err, "failed to read SNAT rule list to get default SNAT ip", "VPC", vpc.Id) + return "", err + } + + if results.Results == nil || len(results.Results) == 0 { + log.Info("no SNAT rule found under VPC", "VPC", vpc.Id) + return "", nil + } + + // if there is multiple private ip block in vpc, there will also be multiple snat rules, but they are using + // the same snat ip, so just using the first snat rule to get snat ip. + return *results.Results[0].TranslatedNetwork, nil +} + +func (s *VPCService) GetAVISubnetInfo(vpc model.Vpc) (string, string, error) { + subnetsClient := s.NSXClient.SubnetsClient + statusClient := s.NSXClient.SubnetStatusClient + info, err := common.ParseVPCResourcePath(*vpc.Path) + + if err != nil { + return "", "", err + } + + subnet, err := subnetsClient.Get(info.OrgID, info.ProjectID, info.VPCID, common.AVISubnetLBID) + if err != nil { + log.Error(err, "failed to read AVI subnet", "VPC", vpc.Id) + return "", "", err + } + path := *subnet.Path + + statusList, err := statusClient.List(info.OrgID, info.ProjectID, info.VPCID, common.AVISubnetLBID) + if err != nil { + log.Error(err, "failed to read AVI subnet status", "VPC", vpc.Id) + return "", "", err + } + + if len(statusList.Results) == 0 { + log.Info("AVI subnet status not found", "VPC", vpc.Id) + return "", "", err + } + + cidr := *statusList.Results[0].NetworkAddress + log.Info("read AVI subnet properties", "Path", path, "CIDR", cidr) + return path, cidr, nil +} + +func (s *VPCService) CreateorUpdateVPC(obj *v1alpha1.VPC) (*model.Vpc, *VPCNetworkConfigInfo, error) { + // check from VPC store if vpc already exist + updateVpc := false + existingVPC := s.VpcStore.GetVPCsByNamespace(obj.Namespace) + if existingVPC != nil && len(existingVPC) != 0 { // We now consider only one VPC for one namespace + updateVpc = true + log.Info("VPC already exist, updating NSX VPC object", "VPC", existingVPC[0].Id) + } + + // read corresponding vpc network config from store + nc_name, err := s.getNetworkconfigNameFromNS(obj.Namespace) + if err != nil { + log.Error(err, "failed to get network config name for VPC when creating NSX VPC", "VPC", obj.Name) + return nil, nil, err + } + nc, _exist := s.GetVPCNetworkConfig(nc_name) + if !_exist { + message := fmt.Sprintf("failed to read network config %s when creating NSX VPC", nc_name) + log.Info(message) + return nil, nil, errors.New(message) + } + + log.Info("read network config from store", "NetworkConfig", nc_name) + + paths, err := s.CreatOrUpdatePrivateIPBlock(obj, nc) + if err != nil { + log.Error(err, "failed to process private ip blocks, push event back to queue") + return nil, nil, err + } + + // if all private ip blocks are created, then create nsx vpc resource. + nsxVPC := &model.Vpc{} + if updateVpc { + log.Info("VPC resource already exist on NSX, updating VPC", "VPC", existingVPC[0].DisplayName) + nsxVPC = &existingVPC[0] + } else { + log.Info("VPC does not exist on NSX, creating VPC", "VPC", obj.Name) + nsxVPC = nil + } + + createdVpc, err := buildNSXVPC(obj, nc, s.NSXConfig.Cluster, paths, nsxVPC) + if err != nil { + log.Error(err, "failed to build NSX VPC object") + return nil, nil, err + } + + // if there is not change in public cidr and private cidr, build partial vpc will return nil + if createdVpc == nil { + log.Info("no VPC changes detect, skip creating or updating process") + return &existingVPC[0], &nc, nil + } + + log.Info("creating NSX VPC", "VPC", *createdVpc.Id) + err = s.NSXClient.VPCClient.Patch(nc.Org, nc.NsxtProject, *createdVpc.Id, *createdVpc) + if err != nil { + log.Error(err, "failed to create VPC", "Project", nc.NsxtProject, "Namespace", obj.Namespace) + // TODO: this seems to be a nsx bug, in some case, even if nsx returns failed but the object is still created. + // in this condition, we still need to read the object and update it into store, or else operator will create multiple + // vpcs for this namespace. + log.Info("try to read VPC although VPC creation failed", "VPC", *createdVpc.Id) + failedVpc, rErr := s.NSXClient.VPCClient.Get(nc.Org, nc.NsxtProject, *createdVpc.Id) + if rErr != nil { + // failed to read, but already created, we consider this scenario as success, but store may not sync with nsx + log.Info("confirmed VPC is not created", "VPC", createdVpc.Id) + return nil, nil, err + } else { + // vpc created anyway, update store, and in this scenario, we condsider creating successfully + log.Info("read VPCs from NSX after creation failed, still update VPC store", "VPC", *createdVpc.Id) + s.VpcStore.Add(failedVpc) + return &failedVpc, &nc, nil + } + } + + // get the created vpc from nsx, it contains the path of the resources + newVpc, err := s.NSXClient.VPCClient.Get(nc.Org, nc.NsxtProject, *createdVpc.Id) + if err != nil { + // failed to read, but already created, we consider this scenario as success, but store may not sync with nsx + log.Error(err, "failed to read VPC object after creating or updating", "VPC", createdVpc.Id) + return nil, nil, err + } + + realizeService := realizestate.InitializeRealizeState(s.Service) + if err = realizeService.CheckRealizeState(retry.DefaultRetry, *newVpc.Path, "RealizedLogicalRouter"); err != nil { + log.Error(err, "failed to check VPC realization state", "VPC", *createdVpc.Id) + if realizestate.IsRealizeStateError(err) { + log.Error(err, "the created VPC is in error realization state, cleaning the resource", "VPC", *createdVpc.Id) + // delete the nsx vpc object and re-created in next loop + if err := s.DeleteVPC(*newVpc.Path); err != nil { + log.Error(err, "cleanup VPC failed", "VPC", *createdVpc.Id) + return nil, nil, err + } + } + return nil, nil, err + } + + s.VpcStore.Add(newVpc) + return &newVpc, &nc, nil +} + +func (s *VPCService) Cleanup() error { + vpcs := s.ListVPC() + log.Info("cleaning up vpcs", "Count", len(vpcs)) + for _, vpc := range vpcs { + if err := s.DeleteVPC(*vpc.Path); err != nil { + return err + } + } + + ipblocks := s.IpblockStore.List() + log.Info("cleaning up ipblocks", "Count", len(ipblocks)) + for _, ipblock := range ipblocks { + ipb := ipblock.(model.IpAddressBlock) + if err := s.deleteIPBlock(*ipb.Path); err != nil { + return err + } + } + + return nil } diff --git a/pkg/nsx/services/vpc/vpc_test.go b/pkg/nsx/services/vpc/vpc_test.go index 459b22574..832d81a10 100644 --- a/pkg/nsx/services/vpc/vpc_test.go +++ b/pkg/nsx/services/vpc/vpc_test.go @@ -55,7 +55,7 @@ func TestVPC_GetVPCsByNamespace(t *testing.T) { service := &VPCService{ Service: common.Service{NSXClient: nil}, } - service.vpcStore = vpcStore + service.VpcStore = vpcStore type args struct { i interface{} j interface{} @@ -100,21 +100,21 @@ func TestVPC_GetVPCsByNamespace(t *testing.T) { } vpc1 := model.Vpc{ - DisplayName: &vpcName1, - Id: &vpcID1, - Tags: tag1, - IpAddressType: &IPv4Type, - PrivateIpv4Blocks: []string{"1.1.1.0/24"}, - PublicIpv4Blocks: []string{"2.2.2.0/24"}, + DisplayName: &vpcName1, + Id: &vpcID1, + Tags: tag1, + IpAddressType: &IPv4Type, + PrivateIpv4Blocks: []string{"1.1.1.0/24"}, + ExternalIpv4Blocks: []string{"2.2.2.0/24"}, } vpc2 := model.Vpc{ - DisplayName: &vpcName2, - Id: &vpcID2, - Tags: tag2, - IpAddressType: &IPv4Type, - PrivateIpv4Blocks: []string{"3.3.3.0/24"}, - PublicIpv4Blocks: []string{"4.4.4.0/24"}, + DisplayName: &vpcName2, + Id: &vpcID2, + Tags: tag2, + IpAddressType: &IPv4Type, + PrivateIpv4Blocks: []string{"3.3.3.0/24"}, + ExternalIpv4Blocks: []string{"4.4.4.0/24"}, } tests := []struct { name string @@ -125,8 +125,8 @@ func TestVPC_GetVPCsByNamespace(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - vpcStore.Operate(&vpc1) - vpcStore.Operate(&vpc2) + vpcStore.Apply(&vpc1) + vpcStore.Apply(&vpc2) got := vpcStore.List() if len(got) != 2 { t.Errorf("size = %v, want %v", len(got), 2) diff --git a/pkg/nsx/transport_test.go b/pkg/nsx/transport_test.go index 17e9b6a7d..61201957b 100644 --- a/pkg/nsx/transport_test.go +++ b/pkg/nsx/transport_test.go @@ -74,7 +74,7 @@ func TestSelectEndpoint(t *testing.T) { assert := assert.New(t) a := "127.0.0.1, 127.0.0.2, 127.0.0.3" config := NewConfig(a, "admin", "passw0rd", []string{}, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, nil, nil, []string{}) - cluster := &Cluster{} + cluster := &Cluster{config: &Config{}} tr := cluster.createTransport(idleConnTimeout) client := cluster.createHTTPClient(tr, timeout) noBClient := cluster.createNoBalancerClient(timeout, idleConnTimeout) @@ -151,7 +151,7 @@ func TestTransport_RoundTrip(t *testing.T) { func Test_handleRoundTripError(t *testing.T) { a := "127.0.0.1, 127.0.0.2, 127.0.0.3" config := NewConfig(a, "admin", "passw0rd", []string{}, 10, 3, 20, 20, true, true, true, ratelimiter.AIMD, nil, nil, []string{}) - cluster := &Cluster{} + cluster := &Cluster{config: &Config{}} tr := cluster.createTransport(idleConnTimeout) client := cluster.createHTTPClient(tr, timeout) noBClient := cluster.createNoBalancerClient(timeout, idleConnTimeout) diff --git a/pkg/nsx/util/errors.go b/pkg/nsx/util/errors.go index 7d737c784..9f02ed2c4 100644 --- a/pkg/nsx/util/errors.go +++ b/pkg/nsx/util/errors.go @@ -570,3 +570,11 @@ type RestrictionError struct { func (err RestrictionError) Error() string { return err.Desc } + +type IPBlockAllExhaustedError struct { + Desc string +} + +func (err IPBlockAllExhaustedError) Error() string { + return err.Desc +} diff --git a/pkg/nsx/util/utils.go b/pkg/nsx/util/utils.go index 6029e7064..92f1cb08c 100644 --- a/pkg/nsx/util/utils.go +++ b/pkg/nsx/util/utils.go @@ -7,7 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "reflect" "sort" @@ -233,20 +233,20 @@ func httpErrortoNSXError(detail *ErrorDetail) NsxError { } func HandleHTTPResponse(response *http.Response, result interface{}, debug bool) (error, []byte) { + body, err := io.ReadAll(response.Body) + defer response.Body.Close() if !(response.StatusCode == http.StatusOK || response.StatusCode == http.StatusAccepted) { err := errors.New("received HTTP Error") - log.Error(err, "handle http response", "status", response.StatusCode, "requestUrl", response.Request.URL, "response", response) + log.Error(err, "handle http response", "status", response.StatusCode, "requestUrl", response.Request.URL, "response body", string(body)) return err, nil } + if err != nil || body == nil { + return err, body + } if result == nil { return nil, nil } - body, err := ioutil.ReadAll(response.Body) - defer response.Body.Close() - if err != nil || body == nil { - return err, body - } if debug { log.V(2).Info("received HTTP response", "response", string(body)) } @@ -276,6 +276,15 @@ func MergeAddressByPort(portAddressOriginal []PortAddress) []PortAddress { return portAddress } +func ParseVPCPath(nsxResourcePath string) (orgID string, projectID string, vpcID string, resourceID string) { + paras := strings.Split(nsxResourcePath, "/") + orgID = paras[2] + projectID = paras[4] + vpcID = paras[6] + resourceID = paras[8] + return +} + // if ApiError is nil, check ErrorTypeEnum, such as ServiceUnavailable // if both return value are nil, the error is not on the list // there is no httpstatus, ApiError does't include it diff --git a/pkg/nsx/util/utils_test.go b/pkg/nsx/util/utils_test.go index 589412da0..c3513cc2a 100644 --- a/pkg/nsx/util/utils_test.go +++ b/pkg/nsx/util/utils_test.go @@ -13,6 +13,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -169,6 +170,7 @@ func TestVCClient_handleHTTPResponse(t *testing.T) { response.Request = &http.Request{} response.Request.URL = &url.URL{Host: "10.0.0.1"} response.StatusCode = 301 + response.Body = io.NopCloser(strings.NewReader("Hello, World!")) var sessionData map[string]string // http status code > 300 diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 57930c761..3ea8f45c0 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -6,20 +6,53 @@ package util import ( "context" "crypto/sha1" + "errors" "fmt" + "net" + "strconv" "strings" + "github.com/apparentlymart/go-cidr/cidr" mapset "github.com/deckarep/golang-set" - "k8s.io/api/core/v1" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha1" + "github.com/vmware-tanzu/nsx-operator/pkg/apis/v1alpha2" "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) -const wcpSystemResource = "vmware-system-shared-t1" -const HashLength int = 8 +const ( + wcpSystemResource = "vmware-system-shared-t1" + HashLength int = 8 + SubnetTypeSubnet = "subnet" + SubnetTypeSubnetSet = "subnetset" +) + +var ( + String = common.String + basicTags = []string{ + common.TagScopeCluster, common.TagScopeVersion, + common.TagScopeStaticRouteCRName, common.TagScopeStaticRouteCRUID, + common.TagScopeSecurityPolicyCRName, common.TagScopeSecurityPolicyCRUID, + common.TagScopeSubnetCRName, common.TagScopeSubnetCRUID, + common.TagScopeSubnetPortCRName, common.TagScopeSubnetPortCRUID, + common.TagScopeVPCCRName, common.TagScopeVPCCRUID, + common.TagScopeIPPoolCRName, common.TagScopeIPPoolCRUID, + common.TagScopeSubnetSetCRName, common.TagScopeSubnetSetCRUID, + } + tagsScopeSet = sets.New[string]() +) + +func init() { + for _, tag := range basicTags { + tagsScopeSet.Insert(tag) + } +} var log = logf.Log.WithName("pkg").WithName("utils") @@ -41,11 +74,15 @@ func NormalizeLabelKey(key string) string { } func NormalizeName(name string) string { - if len(name) <= common.MaxTagLength { + return normalizeNamebyLimit(name, common.MaxTagLength) +} + +func normalizeNamebyLimit(name string, limit int) string { + if len(name) <= limit { return name } hashString := Sha1(name) - nameLength := common.MaxTagLength - common.HashLength - 1 + nameLength := limit - common.HashLength - 1 newName := fmt.Sprintf("%s-%s", name[:nameLength], hashString[:common.HashLength]) return newName } @@ -90,6 +127,11 @@ func ToUpper(obj interface{}) string { return strings.ToUpper(str) } +func CalculateSubnetSize(mask int) int64 { + size := 1 << uint(32-mask) + return int64(size) +} + func IsSystemNamespace(c client.Client, ns string, obj *v1.Namespace) (bool, error) { nsObj := &v1.Namespace{} if obj != nil { @@ -126,3 +168,255 @@ func Contains(s []string, str string) bool { } return false } + +// RemoveIPPrefix remove the prefix from an IP address, e.g. +// "1.2.3.4/24" -> "1.2.3.4" +func RemoveIPPrefix(ipAddress string) (string, error) { + ip := strings.Split(ipAddress, "/")[0] + if net.ParseIP(ip) == nil { + return "", errors.New("invalid IP address") + } + return ip, nil +} + +// GetIPPrefix get the prefix from an IP address, e.g. +// "1.2.3.4/24" -> 24 +func GetIPPrefix(ipAddress string) (int, error) { + num, err := strconv.Atoi(strings.Split(ipAddress, "/")[1]) + if err != nil { + return -1, err + } + return num, err +} + +// GetSubnetMask get the mask for a given prefix length, e.g. +// 24 -> "255.255.255.0" +func GetSubnetMask(subnetLength int) (string, error) { + if subnetLength < 0 || subnetLength > 32 { + return "", errors.New("invalid subnet mask length") + } + // Create a 32-bit subnet mask with leading 1's and trailing 0's + subnetBinary := uint32(0xffffffff) << (32 - subnetLength) + // Convert the binary representation to dotted-decimal format + subnetMask := net.IPv4(byte(subnetBinary>>24), byte(subnetBinary>>16), byte(subnetBinary>>8), byte(subnetBinary)) + return subnetMask.String(), nil +} + +func CalculateIPFromCIDRs(IPAddresses []string) (int, error) { + total := 0 + for _, addr := range IPAddresses { + mask, err := strconv.Atoi(strings.Split(addr, "/")[1]) + if err != nil { + return -1, err + } + total += int(cidr.AddressCount(&net.IPNet{ + IP: net.ParseIP(strings.Split(addr, "/")[0]), + Mask: net.CIDRMask(mask, 32), + })) + } + return total, nil +} + +func If(condition bool, trueVal, falseVal interface{}) interface{} { + if condition { + return trueVal + } else { + return falseVal + } +} + +func GetMapValues(in interface{}) []string { + if in == nil { + return make([]string, 0) + } + switch in.(type) { + case map[string]string: + ssMap := in.(map[string]string) + values := make([]string, 0, len(ssMap)) + for _, v := range ssMap { + values = append(values, v) + } + return values + default: + log.Info("Unsupported map format") + return nil + } +} + +// the changes map contains key/value map that you want to change. +// if giving empty value for a key in changes map like: "mykey":"", that means removing this annotation from k8s resource +func UpdateK8sResourceAnnotation(client client.Client, ctx *context.Context, k8sObj client.Object, changes map[string]string) error { + needUpdate := false + anno := k8sObj.GetAnnotations() // here it may return a nil because ns do not have annotations. + newAnno := If(anno == nil, map[string]string{}, anno).(map[string]string) + for key, value := range changes { + // if value is not none, it means this key/value need to add/update + if value != "" { + needUpdate = true + newAnno[key] = value + } else { // if value is empty, then this key/value need to be removed from map + _, exist := newAnno[key] + if exist { + delete(newAnno, key) + needUpdate = true + } else { + log.Info("No need to change ns annotation") + needUpdate = false + } + } + } + // update k8s object + k8sObj.SetAnnotations(newAnno) + + // only send update request when it is needed + if needUpdate { + err := client.Update(*ctx, k8sObj) + if err != nil { + return err + } + } + return nil +} + +// GenerateID generate id for nsx resource, some resources has complex index, so set it type to string +func GenerateID(res_id, prefix, suffix string, index string) string { + var id strings.Builder + if len(prefix) > 0 { + id.WriteString(prefix) + id.WriteString("_") + } + + id.WriteString(res_id) + if len(index) > 0 { + id.WriteString("_") + id.WriteString(index) + + } + if len(suffix) > 0 { + id.WriteString("_") + id.WriteString(suffix) + } + return id.String() +} + +func GenerateDisplayName(res_name, prefix, suffix, project, cluster string) string { + var name strings.Builder + if len(prefix) > 0 { + name.WriteString(prefix) + name.WriteString("-") + } + if len(cluster) > 0 { + name.WriteString(cluster) + name.WriteString("-") + + } + name.WriteString(res_name) + if len(project) > 0 { + name.WriteString("-") + name.WriteString(project) + + } + + if len(suffix) > 0 { + name.WriteString("-") + name.WriteString(suffix) + } + return name.String() +} + +func GenerateTruncName(limit int, res_name, prefix, suffix, project, cluster string) string { + adjusted_limit := limit - len(prefix) - len(suffix) + for _, i := range []string{prefix, suffix} { + if len(i) > 0 { + adjusted_limit -= 1 + } + } + old_name := GenerateDisplayName(res_name, "", "", project, cluster) + if len(old_name) > adjusted_limit { + new_name := normalizeNamebyLimit( + old_name, adjusted_limit) + return GenerateDisplayName(new_name, prefix, suffix, "", "") + } + return GenerateDisplayName(res_name, prefix, suffix, project, cluster) +} + +func BuildBasicTags(cluster string, obj interface{}, namespaceID types.UID) []model.Tag { + tags := []model.Tag{ + { + Scope: String(common.TagScopeCluster), + Tag: String(cluster), + }, + { + Scope: String(common.TagScopeVersion), + Tag: String(strings.Join(common.TagValueVersion, ".")), + }, + } + isVmSubnetPort := false + switch i := obj.(type) { + case *v1alpha1.StaticRoute: + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeStaticRouteCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeStaticRouteCRUID), Tag: String(string(i.UID))}) + case *v1alpha1.SecurityPolicy: + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSecurityPolicyCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSecurityPolicyCRUID), Tag: String(string(i.UID))}) + case *v1alpha1.Subnet: + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetCRUID), Tag: String(string(i.UID))}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetCRType), Tag: String(SubnetTypeSubnet)}) + case *v1alpha1.SubnetSet: + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetSetCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetSetCRUID), Tag: String(string(i.UID))}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetCRType), Tag: String(SubnetTypeSubnetSet)}) + case *v1alpha1.SubnetPort: + tags = append(tags, model.Tag{Scope: String(common.TagScopeVMNamespace), Tag: String(i.ObjectMeta.Namespace)}) + isVmSubnetPort = true + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetPortCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeSubnetPortCRUID), Tag: String(string(i.UID))}) + case *v1.Pod: + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopePodName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopePodUID), Tag: String(string(i.UID))}) + case *v1alpha1.VPC: + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeVPCCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeVPCCRUID), Tag: String(string(i.UID))}) + case *v1alpha2.IPPool: + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespace), Tag: String(i.ObjectMeta.Namespace)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeIPPoolCRName), Tag: String(i.ObjectMeta.Name)}) + tags = append(tags, model.Tag{Scope: String(common.TagScopeIPPoolCRUID), Tag: String(string(i.UID))}) + default: + log.Info("unknown obj type", "obj", obj) + } + + if len(namespaceID) > 0 { + if isVmSubnetPort == true { + // In the NSX subnet port created for VM, the namespace uid tag is TagScopeVMNamespaceUID instead of TagScopeNamespaceUID. + tags = append(tags, model.Tag{Scope: String(common.TagScopeVMNamespaceUID), Tag: String(string(namespaceID))}) + } else { + tags = append(tags, model.Tag{Scope: String(common.TagScopeNamespaceUID), Tag: String(string(namespaceID))}) + } + } + return tags +} + +func AppendTags(basicTags, extraTags []model.Tag) []model.Tag { + if basicTags == nil { + log.Info("AppendTags", "basicTags", basicTags, "extra tags", extraTags) + return nil + } + for _, tag := range extraTags { + if !tagsScopeSet.Has(*tag.Scope) { + basicTags = append(basicTags, tag) + } + } + return basicTags +} + +func Capitalize(s string) string { + if s == "" { + return "" + } + return strings.ToUpper(s[:1]) + s[1:] +} diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index 5c7099cf0..4fba02515 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -210,6 +210,24 @@ func TestRemoveDuplicateStr(t *testing.T) { } } +func TestCalculateSubnetSize(t *testing.T) { + type args struct { + mask int + } + tests := []struct { + name string + args args + want int64 + }{ + {"1", args{24}, 256}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, CalculateSubnetSize(tt.args.mask), "CalculateSubnetSize(%v)", tt.args.mask) + }) + } +} + func TestNormalizeId(t *testing.T) { type args struct { name string @@ -249,3 +267,231 @@ func TestNormalizeId(t *testing.T) { }) } } + +func TestGenerateID(t *testing.T) { + type args struct { + res_id string + prefix string + suffix string + index string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test-1", + args: args{ + res_id: "1234-456", + prefix: "sp", + suffix: "", + index: "", + }, + want: "sp_1234-456", + }, + { + name: "test-subfix", + args: args{ + res_id: "1234-456", + prefix: "sp", + suffix: "scope", + index: "", + }, + want: "sp_1234-456_scope", + }, + { + name: "test-index", + args: args{ + res_id: "1234-456", + prefix: "sp", + suffix: "scope", + index: "4", + }, + want: "sp_1234-456_4_scope", + }, + { + name: "test-scope", + args: args{ + res_id: "1234-456", + prefix: "", + suffix: "scope", + index: "", + }, + want: "1234-456_scope", + }, + { + name: "test-complex-index", + args: args{ + res_id: "1234-456", + prefix: "", + suffix: "scope", + index: "6_7", + }, + want: "1234-456_6_7_scope", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GenerateID(tt.args.res_id, tt.args.prefix, tt.args.suffix, tt.args.index); got != tt.want { + t.Errorf("GenerateID() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGenerateDisplayName(t *testing.T) { + type args struct { + res_name string + prefix string + suffix string + project string + cluster string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test-1", + args: args{ + res_name: "1234-456", + prefix: "sp", + suffix: "", + project: "", + }, + want: "sp-1234-456", + }, + { + name: "test-suffix", + args: args{ + res_name: "1234-456", + prefix: "sp", + suffix: "scope", + project: "", + }, + want: "sp-1234-456-scope", + }, + { + name: "test-index", + args: args{ + res_name: "1234-456", + prefix: "sp", + suffix: "scope", + project: "test", + }, + want: "sp-1234-456-test-scope", + }, + { + name: "test-cluster", + args: args{ + res_name: "1234-456", + prefix: "", + suffix: "scope", + project: "", + cluster: "k8scl-one", + }, + want: "k8scl-one-1234-456-scope", + }, + { + name: "test-project-cluster", + args: args{ + res_name: "1234-456", + prefix: "", + suffix: "scope", + project: "test", + cluster: "k8scl-one", + }, + want: "k8scl-one-1234-456-test-scope", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GenerateDisplayName(tt.args.res_name, tt.args.prefix, tt.args.suffix, tt.args.project, tt.args.cluster); got != tt.want { + t.Errorf("GenerateDisplayName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGenerateTruncName(t *testing.T) { + type args struct { + limit int + res_name string + prefix string + suffix string + project string + cluster string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test-1", + args: args{ + limit: 255, + res_name: "1234-456", + prefix: "sp", + suffix: "", + project: "", + }, + want: "sp-1234-456", + }, + { + name: "test-suffix", + args: args{ + limit: 255, + res_name: "1234-456", + prefix: "sp", + suffix: "scope", + project: "", + }, + want: "sp-1234-456-scope", + }, + { + name: "test-index", + args: args{ + limit: 255, + res_name: "1234-456", + prefix: "sp", + suffix: "scope", + project: "test", + }, + want: "sp-1234-456-test-scope", + }, + { + name: "test-cluster", + args: args{ + limit: 255, + res_name: "1234-456", + prefix: "", + suffix: "scope", + project: "", + cluster: "k8scl-one", + }, + want: "k8scl-one-1234-456-scope", + }, + { + name: "test-project-cluster", + args: args{ + limit: 255, + res_name: "1234-456", + prefix: "sr", + suffix: "scope", + project: strings.Repeat("s", 300), + cluster: "k8scl-one", + }, + want: "sr-k8scl-one-1234-456-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss-813dffe8-scope", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GenerateTruncName(tt.args.limit, tt.args.res_name, tt.args.prefix, tt.args.suffix, tt.args.project, tt.args.cluster); got != tt.want { + t.Errorf("GenerateTruncName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test/e2e/framework.go b/test/e2e/framework.go index c76599d35..6bb6a9635 100644 --- a/test/e2e/framework.go +++ b/test/e2e/framework.go @@ -29,7 +29,9 @@ import ( ) const ( - defaultTimeout = 100 * time.Second + defaultTimeout = 100 * time.Second + verifyNoneExistTimeout = 15 * time.Second + crdVersion = "v1alpha1" ) type Status int @@ -355,11 +357,11 @@ func (data *TestData) deletePodAndWait(timeout time.Duration, name string, ns st type PodCondition func(*corev1.Pod) (bool, error) -// waitForSecurityPolicyReady polls the K8s apiServer until the specified SecurityPolicy is in the "True" state (or until +// waitForSecurityPolicyReady polls the K8s apiServer until the specified CR is in the "True" state (or until // the provided timeout expires). -func (data *TestData) waitForSecurityPolicyReadyOrDeleted(timeout time.Duration, namespace string, name string, status Status) error { +func (data *TestData) waitForCRReadyOrDeleted(timeout time.Duration, cr string, namespace string, name string, status Status) error { err := wait.Poll(1*time.Second, timeout, func() (bool, error) { - cmd := fmt.Sprintf("kubectl get securitypolicy %s -n %s -o jsonpath='{.status.conditions[?(@.type==\"Ready\")].status}'", name, namespace) + cmd := fmt.Sprintf("kubectl get %s %s -n %s -o jsonpath='{.status.conditions[?(@.type==\"Ready\")].status}'", cr, name, namespace) log.Printf("%s", cmd) rc, stdout, _, err := RunCommandOnNode(clusterInfo.masterNodeName, cmd) if err != nil || rc != 0 { @@ -383,6 +385,71 @@ func (data *TestData) waitForSecurityPolicyReadyOrDeleted(timeout time.Duration, return nil } +func (data *TestData) getCRProperties(timeout time.Duration, crType, crName, namespace, key string) (string, error) { + value := "" + err := wait.Poll(1*time.Second, timeout, func() (bool, error) { + cmd := fmt.Sprintf("kubectl get %s %s -n %s -o yaml | grep %s", crType, crName, namespace, key) + log.Printf("%s", cmd) + rc, stdout, _, err := RunCommandOnNode(clusterInfo.masterNodeName, cmd) + if err != nil || rc != 0 { + return false, fmt.Errorf("error when running the following command `%s` on master Node: %v, %s", cmd, err, stdout) + } else { + parts := strings.Split(stdout, ":") + if len(parts) != 2 { + return false, fmt.Errorf("failed to read attribute from output %s", stdout) + } else { + value = parts[1] + return true, nil + } + } + }) + if err != nil { + return value, err + } + return value, nil +} + +// Check if CR is created under NS, for resources like VPC, we do not know CR name +// return map structure, key is CR name, value is CR UID +func (data *TestData) getCRResource(timeout time.Duration, cr string, namespace string) (map[string]string, error) { + crs := map[string]string{} + err := wait.Poll(1*time.Second, timeout, func() (bool, error) { + cmd := fmt.Sprintf("kubectl get %s -n %s", cr, namespace) + log.Printf("%s", cmd) + rc, stdout, _, err := RunCommandOnNode(clusterInfo.masterNodeName, cmd) + if err != nil || rc != 0 { + return false, fmt.Errorf("error when running the following command `%s` on master Node: %v, %s", cmd, err, stdout) + } else { + crs_raw := strings.Split(stdout, "\n") + // for each resource, get json structure as value + for i, c := range crs_raw { + if i == 0 { + // first line is table header + continue + } + r := regexp.MustCompile("[^\\s]+") + parts := r.FindAllString(c, -1) + if len(parts) < 1 { // to avoid empty lines + continue + } + uid_cmd := fmt.Sprintf("kubectl get %s %s -n %s -o yaml | grep uid", cr, parts[0], namespace) + log.Printf("trying to get uid for cr: %s", uid_cmd) + rc, stdout, _, err := RunCommandOnNode(clusterInfo.masterNodeName, uid_cmd) + if err != nil || rc != 0 { + return false, fmt.Errorf("error when running the following command `%s` on master Node: %v, %s", uid_cmd, err, stdout) + } + uid := strings.Split(stdout, ":")[1] + crs[parts[0]] = uid + } + return true, nil + } + }) + if err != nil { + return crs, err + } + return crs, nil +} + // podWaitFor polls the K8s apiServer until the specified Pod is found (in the test Namespace) and // the condition predicate is met (or until the provided timeout expires). func (data *TestData) podWaitFor(timeout time.Duration, name, namespace string, condition PodCondition) (*corev1.Pod, error) { @@ -636,25 +703,25 @@ func deleteYAML(filename string, ns string) error { return nil } -func (data *TestData) waitForResourceExistOrNot(namespace string, resourceType string, resourceName string, shouldExist bool) error { +func (data *TestData) waitForResourceExist(namespace string, resourceType string, key string, value string, shouldExist bool) error { err := wait.Poll(1*time.Second, defaultTimeout, func() (bool, error) { exist := true tagScopeClusterKey := strings.Replace(common.TagScopeNamespace, "/", "\\/", -1) tagScopeClusterValue := strings.Replace(namespace, ":", "\\:", -1) tagParam := fmt.Sprintf("tags.scope:%s AND tags.tag:%s", tagScopeClusterKey, tagScopeClusterValue) - resourceParam := fmt.Sprintf("%s:%s AND display_name:*%s*", common.ResourceType, resourceType, resourceName) + resourceParam := fmt.Sprintf("%s:%s AND %s:*%s*", common.ResourceType, resourceType, key, value) queryParam := resourceParam + " AND " + tagParam var cursor *string = nil var pageSize int64 = 500 response, err := testData.nsxClient.QueryClient.List(queryParam, cursor, nil, &pageSize, nil, nil) if err != nil { - log.Printf("Error when querying resource %s/%s: %v", resourceType, resourceName, err) + log.Printf("Error when querying resource %s/%s: %s,%v", resourceType, key, value, err) return false, err } if len(response.Results) == 0 { exist = false } - //log.Printf("QueryParam: %s Result: %t", queryParam, exist) + log.Printf("QueryParam: %s exist: %t", queryParam, exist) if exist != shouldExist { return false, nil } @@ -662,3 +729,11 @@ func (data *TestData) waitForResourceExistOrNot(namespace string, resourceType s }) return err } + +func (data *TestData) waitForResourceExistById(namespace string, resourceType string, id string, shouldExist bool) error { + return data.waitForResourceExist(namespace, resourceType, "id", id, shouldExist) +} + +func (data *TestData) waitForResourceExistOrNot(namespace string, resourceType string, resourceName string, shouldExist bool) error { + return data.waitForResourceExist(namespace, resourceType, "display_name", resourceName, shouldExist) +} diff --git a/test/e2e/manifest/common/virtualmachine.yaml b/test/e2e/manifest/common/virtualmachine.yaml new file mode 100644 index 000000000..bc452a811 --- /dev/null +++ b/test/e2e/manifest/common/virtualmachine.yaml @@ -0,0 +1,596 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualmachines.vmoperator.vmware.com +spec: + conversion: + strategy: None + group: vmoperator.vmware.com + names: + kind: VirtualMachine + listKind: VirtualMachineList + plural: virtualmachines + shortNames: + - vm + singular: virtualmachine + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.powerState + name: Power-State + type: string + - jsonPath: .spec.className + name: Class + priority: 1 + type: string + - jsonPath: .spec.imageName + name: Image + priority: 1 + type: string + - jsonPath: .status.vmIp + name: Primary-IP + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: VirtualMachine is the Schema for the virtualmachines API. A VirtualMachine + represents the desired specification and the observed status of a VirtualMachine + instance. A VirtualMachine is realized by the VirtualMachine controller + on a backing Virtual Infrastructure provider such as vSphere. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VirtualMachineSpec defines the desired state of a VirtualMachine. + properties: + advancedOptions: + description: AdvancedOptions describes a set of optional, advanced + options for configuring a VirtualMachine + properties: + changeBlockTracking: + description: ChangeBlockTracking specifies the enablement of incremental + backup support for this VirtualMachine, which can be utilized + by external backup systems such as VMware Data Recovery. + type: boolean + defaultVolumeProvisioningOptions: + description: DefaultProvisioningOptions specifies the provisioning + type to be used by default for VirtualMachine volumes exclusively + owned by this VirtualMachine. This does not apply to PersistentVolumeClaim + volumes that are created and managed externally. + properties: + eagerZeroed: + description: EagerZeroed specifies whether to use eager zero + provisioning for the VirtualMachineVolume. An eager zeroed + thick disk has all space allocated and wiped clean of any + previous contents on the physical media at creation time. + Such disks may take longer time during creation compared + to other disk formats. EagerZeroed is only applicable if + ThinProvisioned is false. This is validated by the webhook. + type: boolean + thinProvisioned: + description: ThinProvisioned specifies whether to use thin + provisioning for the VirtualMachineVolume. This means a + sparse (allocate on demand) format with additional space + optimizations. + type: boolean + type: object + type: object + className: + description: ClassName describes the name of a VirtualMachineClass + that is to be used as the overlaid resource configuration of VirtualMachine. A + VirtualMachineClass is used to further customize the attributes + of the VirtualMachine instance. See VirtualMachineClass for more + description. + type: string + imageName: + description: ImageName describes the name of a VirtualMachineImage + that is to be used as the base Operating System image of the desired + VirtualMachine instances. The VirtualMachineImage resources can + be introspected to discover identifying attributes that may help + users to identify the desired image to use. + type: string + networkInterfaces: + description: NetworkInterfaces describes a list of VirtualMachineNetworkInterfaces + to be configured on the VirtualMachine instance. Each of these VirtualMachineNetworkInterfaces + describes external network integration configurations that are to + be used by the VirtualMachine controller when integrating the VirtualMachine + into one or more external networks. + items: + description: VirtualMachineNetworkInterface defines the properties + of a network interface to attach to a VirtualMachine instance. A + VirtualMachineNetworkInterface describes network interface configuration + that is used by the VirtualMachine controller when integrating + the VirtualMachine into a VirtualNetwork. Currently, only NSX-T + and vSphere Distributed Switch (VDS) type network integrations + are supported using this VirtualMachineNetworkInterface structure. + properties: + ethernetCardType: + description: EthernetCardType describes an optional ethernet + card that should be used by the VirtualNetworkInterface (vNIC) + associated with this network integration. The default is + "vmxnet3". + type: string + networkName: + description: NetworkName describes the name of an existing virtual + network that this interface should be added to. For "nsx-t" + NetworkType, this is the name of a pre-existing NSX-T VirtualNetwork. + If unspecified, the default network for the namespace will + be used. For "vsphere-distributed" NetworkType, the NetworkName + must be specified. + type: string + networkType: + description: NetworkType describes the type of VirtualNetwork + that is referenced by the NetworkName. Currently, the only + supported NetworkTypes are "nsx-t" and "vsphere-distributed". + type: string + providerRef: + description: ProviderRef is reference to a network interface + provider object that specifies the network interface configuration. + If unset, default configuration is assumed. + properties: + apiGroup: + description: APIGroup is the group for the resource being + referenced. + type: string + apiVersion: + description: API version of the referent. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - apiGroup + - kind + - name + type: object + type: object + type: array + nextRestartTime: + description: "NextRestartTime may be used to restart the VM, in accordance + with RestartMode, by setting the value of this field to \"now\" + (case-insensitive). \n A mutating webhook changes this value to + the current time (UTC), which the VM controller then uses to determine + the VM should be restarted by comparing the value to the timestamp + of the last time the VM was restarted. \n Please note it is not + possible to schedule future restarts using this field. The only + value that users may set is the string \"now\" (case-insensitive)." + type: string + ports: + description: Ports is currently unused and can be considered deprecated. + items: + description: VirtualMachinePort is unused and can be considered + deprecated. + properties: + ip: + type: string + name: + type: string + port: + type: integer + protocol: + default: TCP + type: string + required: + - ip + - name + - port + - protocol + type: object + type: array + powerOffMode: + default: hard + description: "PowerOffMode describes the desired behavior when powering + off a VM. \n There are three, supported power off modes: hard, soft, + and trySoft. The first mode, hard, is the equivalent of a physical + system's power cord being ripped from the wall. The soft mode requires + the VM's guest to have VM Tools installed and attempts to gracefully + shutdown the VM. Its variant, trySoft, first attempts a graceful + shutdown, and if that fails or the VM is not in a powered off state + after five minutes, the VM is halted. \n If omitted, the mode defaults + to hard." + enum: + - hard + - soft + - trySoft + type: string + powerState: + description: "PowerState describes the desired power state of a VirtualMachine. + \n Please note this field may be omitted when creating a new VM + and will default to \"poweredOn.\" However, once the field is set + to a non-empty value, it may no longer be set to an empty value. + \n Additionally, setting this value to \"suspended\" is not supported + when creating a new VM. The valid values when creating a new VM + are \"poweredOn\" and \"poweredOff.\" An empty value is also allowed + on create since this value defaults to \"poweredOn\" for new VMs." + enum: + - poweredOn + - poweredOff + - suspended + type: string + readinessProbe: + description: ReadinessProbe describes a network probe that can be + used to determine if the VirtualMachine is available and responding + to the probe. + properties: + guestHeartbeat: + description: GuestHeartbeat specifies an action involving the + guest heartbeat status. + properties: + thresholdStatus: + default: green + description: ThresholdStatus is the value that the guest heartbeat + status must be at or above to be considered successful. + enum: + - yellow + - green + type: string + type: object + periodSeconds: + description: PeriodSeconds specifics how often (in seconds) to + perform the probe. Defaults to 10 seconds. Minimum value is + 1. + format: int32 + minimum: 1 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + properties: + host: + description: Host is an optional host name to connect to. Host + defaults to the VirtualMachine IP. + type: string + port: + anyOf: + - type: integer + - type: string + description: Port specifies a number or name of the port to + access on the VirtualMachine. If the format of port is a + number, it must be in the range 1 to 65535. If the format + of name is a string, it must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: TimeoutSeconds specifies a number of seconds after + which the probe times out. Defaults to 10 seconds. Minimum value + is 1. + format: int32 + maximum: 60 + minimum: 1 + type: integer + type: object + resourcePolicyName: + description: ResourcePolicyName describes the name of a VirtualMachineSetResourcePolicy + to be used when creating the VirtualMachine instance. + type: string + restartMode: + default: hard + description: "RestartMode describes the desired behavior for restarting + a VM when spec.nextRestartTime is set to \"now\" (case-insensitive). + \n There are three, supported suspend modes: hard, soft, and trySoft. + The first mode, hard, is where vSphere resets the VM without any + interaction inside of the guest. The soft mode requires the VM's + guest to have VM Tools installed and asks the guest to restart the + VM. Its variant, trySoft, first attempts a soft restart, and if + that fails or does not complete within five minutes, the VM is hard + reset. \n If omitted, the mode defaults to hard." + enum: + - hard + - soft + - trySoft + type: string + storageClass: + description: StorageClass describes the name of a StorageClass that + should be used to configure storage-related attributes of the VirtualMachine + instance. + type: string + suspendMode: + default: hard + description: "SuspendMode describes the desired behavior when suspending + a VM. \n There are three, supported suspend modes: hard, soft, and + trySoft. The first mode, hard, is where vSphere suspends the VM + to disk without any interaction inside of the guest. The soft mode + requires the VM's guest to have VM Tools installed and attempts + to gracefully suspend the VM. Its variant, trySoft, first attempts + a graceful suspend, and if that fails or the VM is not in a put + into standby by the guest after five minutes, the VM is suspended. + \n If omitted, the mode defaults to hard." + enum: + - hard + - soft + - trySoft + type: string + vmMetadata: + description: VmMetadata describes any optional metadata that should + be passed to the Guest OS. + properties: + configMapName: + description: ConfigMapName describes the name of the ConfigMap, + in the same Namespace as the VirtualMachine, that should be + used for VirtualMachine metadata. The contents of the Data + field of the ConfigMap is used as the VM Metadata. The format + of the contents of the VM Metadata are not parsed or interpreted + by the VirtualMachine controller. Please note, this field and + SecretName are mutually exclusive. + type: string + secretName: + description: SecretName describes the name of the Secret, in the + same Namespace as the VirtualMachine, that should be used for + VirtualMachine metadata. The contents of the Data field of the + Secret is used as the VM Metadata. The format of the contents + of the VM Metadata are not parsed or interpreted by the VirtualMachine + controller. Please note, this field and ConfigMapName are mutually + exclusive. + type: string + transport: + description: Transport describes the name of a supported VirtualMachineMetadata + transport protocol. Currently, the only supported transport + protocols are "ExtraConfig", "OvfEnv" and "CloudInit". + enum: + - ExtraConfig + - OvfEnv + - vAppConfig + - CloudInit + - Sysprep + type: string + type: object + volumes: + description: Volumes describes the list of VirtualMachineVolumes that + are desired to be attached to the VirtualMachine. Each of these + volumes specifies a volume identity that the VirtualMachine controller + will attempt to satisfy, potentially with an external Volume Management + service. + items: + description: VirtualMachineVolume describes a Volume that should + be attached to a specific VirtualMachine. Only one of PersistentVolumeClaim, + VsphereVolume should be specified. + properties: + name: + description: Name specifies the name of the VirtualMachineVolume. Each + volume within the scope of a VirtualMachine must have a unique + name. + type: string + persistentVolumeClaim: + description: "PersistentVolumeClaim represents a reference to + a PersistentVolumeClaim in the same namespace. The PersistentVolumeClaim + must match one of the following: \n * A volume provisioned + (either statically or dynamically) by the cluster's CSI provider. + \n * An instance volume with a lifecycle coupled to the VM." + properties: + claimName: + description: 'claimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + instanceVolumeClaim: + description: InstanceVolumeClaim is set if the PVC is backed + by instance storage. + properties: + size: + anyOf: + - type: integer + - type: string + description: Size is the size of the requested instance + storage volume. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + storageClass: + description: StorageClass is the name of the Kubernetes + StorageClass that provides the backing storage for + this instance storage volume. + type: string + required: + - size + - storageClass + type: object + readOnly: + description: readOnly Will force the ReadOnly setting in + VolumeMounts. Default false. + type: boolean + required: + - claimName + type: object + vSphereVolume: + description: VsphereVolume represents a reference to a VsphereVolumeSource + in the same namespace. Only one of PersistentVolumeClaim or + VsphereVolume can be specified. This is enforced via a webhook + properties: + capacity: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: A description of the virtual volume's resources + and capacity + type: object + deviceKey: + description: Device key of vSphere disk. + type: integer + type: object + required: + - name + type: object + type: array + required: + - className + - imageName + type: object + status: + description: VirtualMachineStatus defines the observed state of a VirtualMachine + instance. + properties: + biosUUID: + description: BiosUUID describes a unique identifier provided by the + underlying infrastructure provider that is exposed to the Guest + OS BIOS as a unique hardware identifier. + type: string + changeBlockTracking: + description: ChangeBlockTracking describes the CBT enablement status + on the VirtualMachine. + type: boolean + conditions: + description: Conditions describes the current condition information + of the VirtualMachine. + items: + description: Condition defines an observation of a VM Operator API + resource operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to disambiguate + is important. + type: string + required: + - status + - type + type: object + type: array + host: + description: Host describes the hostname or IP address of the infrastructure + host that the VirtualMachine is executing on. + type: string + instanceUUID: + description: InstanceUUID describes the unique instance UUID provided + by the underlying infrastructure provider, such as vSphere. + type: string + lastRestartTime: + description: LastRestartTime describes the last time the VM was restarted. + format: date-time + type: string + networkInterfaces: + description: NetworkInterfaces describes a list of current status + information for each network interface that is desired to be attached + to the VirtualMachine. + items: + description: NetworkInterfaceStatus defines the observed state of + network interfaces attached to the VirtualMachine as seen by the + Guest OS and VMware tools. + properties: + connected: + description: Connected represents whether the network interface + is connected or not. + type: boolean + ipAddresses: + description: IpAddresses represents zero, one or more IP addresses + assigned to the network interface in CIDR notation. For eg, + "192.0.2.1/16". + items: + type: string + type: array + macAddress: + description: MAC address of the network adapter + type: string + required: + - connected + type: object + type: array + phase: + description: Phase describes the current phase information of the + VirtualMachine. + type: string + powerState: + description: PowerState describes the current power state of the VirtualMachine. + enum: + - poweredOn + - poweredOff + - suspended + type: string + uniqueID: + description: UniqueID describes a unique identifier that is provided + by the underlying infrastructure provider, such as vSphere. + type: string + vmIp: + description: VmIp describes the Primary IP address assigned to the + guest operating system, if known. Multiple IPs can be available + for the VirtualMachine. Refer to networkInterfaces in the VirtualMachine + status for additional IPs + type: string + volumes: + description: Volumes describes a list of current status information + for each Volume that is desired to be attached to the VirtualMachine. + items: + description: VirtualMachineVolumeStatus defines the observed state + of a VirtualMachineVolume instance. + properties: + attached: + description: Attached represents whether a volume has been successfully + attached to the VirtualMachine or not. + type: boolean + diskUUID: + description: DiskUuid represents the underlying virtual disk + UUID and is present when attachment succeeds. + type: string + error: + description: Error represents the last error seen when attaching + or detaching a volume. Error will be empty if attachment + succeeds. + type: string + name: + description: Name is the name of the volume in a VirtualMachine. + type: string + required: + - attached + - diskUUID + - error + - name + type: object + type: array + zone: + description: Zone describes the availability zone where the VirtualMachine + has been scheduled. Please note this field may be empty when the + cluster is not zone-aware. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} \ No newline at end of file diff --git a/test/e2e/manifest/testIPPool/ippool.yaml b/test/e2e/manifest/testIPPool/ippool.yaml new file mode 100644 index 000000000..ee471552f --- /dev/null +++ b/test/e2e/manifest/testIPPool/ippool.yaml @@ -0,0 +1,16 @@ +apiVersion: nsx.vmware.com/v1alpha2 +kind: IPPool +metadata: + name: guestcluster-ippool-2 +spec: + type: Public + subnets: + - ipFamily: IPv4 + name: guestcluster1-workers-a + prefixLength: 26 + - ipFamily: IPv4 + name: guestcluster1-workers-b + prefixLength: 26 + - ipFamily: IPv4 + name: guestcluster1-workers-c + prefixLength: 26 \ No newline at end of file diff --git a/test/e2e/manifest/testIPPool/ippool_delete.yaml b/test/e2e/manifest/testIPPool/ippool_delete.yaml new file mode 100644 index 000000000..fcacf0b46 --- /dev/null +++ b/test/e2e/manifest/testIPPool/ippool_delete.yaml @@ -0,0 +1,10 @@ +apiVersion: nsx.vmware.com/v1alpha2 +kind: IPPool +metadata: + name: guestcluster-ippool-2 +spec: + type: Public + subnets: + - ipFamily: IPv4 + name: guestcluster1-workers-a + prefixLength: 26 \ No newline at end of file diff --git a/test/e2e/manifest/testIPPool/ippool_subnet_nil.yaml b/test/e2e/manifest/testIPPool/ippool_subnet_nil.yaml new file mode 100644 index 000000000..343f4cc22 --- /dev/null +++ b/test/e2e/manifest/testIPPool/ippool_subnet_nil.yaml @@ -0,0 +1,6 @@ +apiVersion: nsx.vmware.com/v1alpha2 +kind: IPPool +metadata: + name: guestcluster-ippool-2 +spec: + type: Public \ No newline at end of file diff --git a/test/e2e/manifest/testVPC/customize_networkconfig.yaml b/test/e2e/manifest/testVPC/customize_networkconfig.yaml new file mode 100644 index 000000000..913c8e46d --- /dev/null +++ b/test/e2e/manifest/testVPC/customize_networkconfig.yaml @@ -0,0 +1,19 @@ +# This file is used in testing customized VPC case, +# it support customer to define its own VPC network config. +apiVersion: nsx.vmware.com/v1alpha1 +kind: VPCNetworkConfiguration +metadata: + name: selfdefinedconfig +spec: + defaultGatewayPath: /infra/tier-0s/PLR + # nsx-operator-ci would replace '{edge-cluster-id}' with real edge-cluster-id of testbed + edgeClusterPath: /infra/sites/default/enforcement-points/default/edge-clusters/{edge-cluster-id} + defaultIPv4SubnetSize: 26 + nsxtProject: /orgs/default/projects/nsx_operator_e2e_test + externalIPv4Blocks: + - /infra/ip-blocks/e2e_test_external_ip_blk + privateIPv4CIDRs: + - 172.29.0.0/16 + - 172.39.0.0/16 + defaultSubnetAccessMode: Public + \ No newline at end of file diff --git a/test/e2e/manifest/testVPC/customize_ns.yaml b/test/e2e/manifest/testVPC/customize_ns.yaml new file mode 100644 index 000000000..6a12863d0 --- /dev/null +++ b/test/e2e/manifest/testVPC/customize_ns.yaml @@ -0,0 +1,8 @@ +# This file is used in testing customized VPC case, +# it create a namespace with customized network config for VPC. +apiVersion: v1 +kind: Namespace +metadata: + annotations: + nsx.vmware.com/vpc_network_config: selfdefinedconfig + name: customized-ns diff --git a/test/e2e/manifest/testVPC/default_networkconfig.yaml b/test/e2e/manifest/testVPC/default_networkconfig.yaml new file mode 100644 index 000000000..aca0d4009 --- /dev/null +++ b/test/e2e/manifest/testVPC/default_networkconfig.yaml @@ -0,0 +1,20 @@ +# This file is used in testing VPC case, +# it should be applied on testbed setup stage, +# any new created namespace that do not have networkconfig specified on annotations +# will use this network config by default +apiVersion: nsx.vmware.com/v1alpha1 +kind: VPCNetworkConfiguration +metadata: + name: default +spec: + defaultGatewayPath: /infra/tier-0s/PLR + # nsx-operator-ci would replace '{edge-cluster-id}' with real edge-cluster-id of testbed + edgeClusterPath: /infra/sites/default/enforcement-points/default/edge-clusters/{edge-cluster-id} + defaultIPv4SubnetSize: 26 + nsxtProject: /orgs/default/projects/nsx_operator_e2e_test + externalIPv4Blocks: + - /infra/ip-blocks/e2e_test_external_ip_blk + privateIPv4CIDRs: + - 172.28.0.0/16 + - 172.38.0.0/16 + defaultSubnetAccessMode: Public diff --git a/test/e2e/manifest/testVPC/infra_networkconfig.yaml b/test/e2e/manifest/testVPC/infra_networkconfig.yaml new file mode 100644 index 000000000..c6af88743 --- /dev/null +++ b/test/e2e/manifest/testVPC/infra_networkconfig.yaml @@ -0,0 +1,20 @@ +# This file is used in testing VPC case, +# it should be applied on testbed setup stage, +# for infra namespaces, they should use this network config. +apiVersion: nsx.vmware.com/v1alpha1 +kind: VPCNetworkConfiguration +metadata: + name: infra +spec: + defaultGatewayPath: /infra/tier-0s/PLR + # nsx-operator-ci would replace '{edge-cluster-id}' with real edge-cluster-id of testbed + edgeClusterPath: /infra/sites/default/enforcement-points/default/edge-clusters/{edge-cluster-id} + defaultIPv4SubnetSize: 26 + nsxtProject: /orgs/default/projects/nsx_operator_e2e_test + externalIPv4Blocks: + - /infra/ip-blocks/e2e_test_external_ip_blk + privateIPv4CIDRs: + - 172.27.0.0/16 + - 172.37.0.0/16 + defaultSubnetAccessMode: Public + \ No newline at end of file diff --git a/test/e2e/nsx_ippool_test.go b/test/e2e/nsx_ippool_test.go new file mode 100644 index 000000000..4bb219743 --- /dev/null +++ b/test/e2e/nsx_ippool_test.go @@ -0,0 +1,152 @@ +// This file is for e2e ippool tests. + +package e2e + +import ( + "path/filepath" + "testing" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" +) + +const ( + IPPool = "ippool" +) + +// TestIPPoolBasic verifies that it could successfully realize ippool subnet from ipblock. +func TestIPPoolBasic(t *testing.T) { + ns := "sc-a" + name := "guestcluster-ippool-2" + subnet_name_1 := "guestcluster1-workers-a" + subnet_name_2 := "guestcluster1-workers-b" + subnet_name_3 := "guestcluster1-workers-c" + setupTest(t, ns) + defer teardownTest(t, ns) + + // Create ippool + ippoolPath, _ := filepath.Abs("./manifest/testIPPool/ippool.yaml") + _ = applyYAML(ippoolPath, ns) + defer deleteYAML(ippoolPath, ns) + + // Check ippool status + err := testData.waitForCRReadyOrDeleted(defaultTimeout, IPPool, ns, name, Ready) + assert_nil(t, err, "Error when waiting for IPPool %s", name) + + // Check nsx-t resource existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_1, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_2, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_3, true) + assert_nil(t, err) + + // Delete ippool + _ = deleteYAML(ippoolPath, ns) + + // Check nsx-t resource not existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, false) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_1, false) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_2, false) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_3, false) + assert_nil(t, err) +} + +// TestIPPoolAddDeleteSubnet verifies that it is as expected when adding or deleting some subnets. +func TestIPPoolAddDeleteSubnet(t *testing.T) { + ns := "sc-a" + name := "guestcluster-ippool-2" + subnet_name_1 := "guestcluster1-workers-a" + subnet_name_2 := "guestcluster1-workers-b" + subnet_name_3 := "guestcluster1-workers-c" + setupTest(t, ns) + defer teardownTest(t, ns) + + // Create ippool + ippoolPath, _ := filepath.Abs("./manifest/testIPPool/ippool.yaml") + _ = applyYAML(ippoolPath, ns) + defer deleteYAML(ippoolPath, ns) + + // Check ippool status + err := testData.waitForCRReadyOrDeleted(defaultTimeout, IPPool, ns, name, Ready) + assert_nil(t, err, "Error when waiting for IPPool %s", name) + + // Check nsx-t resource existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_1, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_2, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_3, true) + assert_nil(t, err) + + // Delete subnet_name_2 and subnet_name_3 + ippoolDeletePath, _ := filepath.Abs("./manifest/testIPPool/ippool_delete.yaml") + _ = applyYAML(ippoolDeletePath, ns) + + // Check ippool status + err = testData.waitForCRReadyOrDeleted(defaultTimeout, IPPool, ns, name, Ready) + assert_nil(t, err, "Error when waiting for IPPool %s", name) + + // Check nsx-t resource existing and not existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, true) + assert_nil(t, err) + // Still existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_1, true) + assert_nil(t, err) + // Deleted + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_2, false) + assert_nil(t, err) + // Deleted + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_3, false) + assert_nil(t, err) + + // Add back subnet_name_2 and subnet_name_3 + _ = applyYAML(ippoolPath, ns) + // Check ippool status + err = testData.waitForCRReadyOrDeleted(defaultTimeout, IPPool, ns, name, Ready) + assert_nil(t, err, "Error when waiting for IPPool %s", name) + + // Check nsx-t resource existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_1, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_2, true) + assert_nil(t, err) + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPoolBlockSubnet, subnet_name_3, true) + assert_nil(t, err) +} + +// TestIPPoolBasic verifies that it could support when subnets are nil +func TestIPPoolSubnetsNil(t *testing.T) { + ns := "sc-a" + name := "guestcluster-ippool-2" + setupTest(t, ns) + defer teardownTest(t, ns) + + // Create ippool + ippoolPath, _ := filepath.Abs("./manifest/testIPPool/ippool.yaml") + _ = applyYAML(ippoolPath, ns) + defer deleteYAML(ippoolPath, ns) + + // Check ippool status + err := testData.waitForCRReadyOrDeleted(defaultTimeout, IPPool, ns, name, Ready) + assert_nil(t, err, "Error when waiting for IPPool %s", name) + + // Check nsx-t resource existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, true) + assert_nil(t, err) + + // Delete ippool + _ = deleteYAML(ippoolPath, ns) + + // Check nsx-t resource not existing + err = testData.waitForResourceExistOrNot(ns, common.ResourceTypeIPPool, name, false) + assert_nil(t, err) +} diff --git a/test/e2e/nsx_security_policy_test.go b/test/e2e/nsx_security_policy_test.go index f1a85951e..446b7588a 100644 --- a/test/e2e/nsx_security_policy_test.go +++ b/test/e2e/nsx_security_policy_test.go @@ -24,6 +24,10 @@ import ( "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) +const ( + SP = "securitypolicy" +) + // TestSecurityPolicyBasicTraffic verifies that the basic traffic of security policy. // This is the very basic, blocking all in and out traffic between pods should take effect. func TestSecurityPolicyBasicTraffic(t *testing.T) { @@ -56,7 +60,7 @@ func TestSecurityPolicyBasicTraffic(t *testing.T) { nsIsolationPath, _ := filepath.Abs("./manifest/testSecurityPolicy/ns-isolation-policy.yaml") _ = applyYAML(nsIsolationPath, ns) defer deleteYAML(nsIsolationPath, ns) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -71,7 +75,7 @@ func TestSecurityPolicyBasicTraffic(t *testing.T) { // Delete security policy _ = deleteYAML(nsIsolationPath, ns) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -100,7 +104,7 @@ func TestSecurityPolicyAddDeleteRule(t *testing.T) { nsIsolationPath, _ := filepath.Abs("./manifest/testSecurityPolicy/ns-isolation-policy.yaml") _ = applyYAML(nsIsolationPath, ns) defer deleteYAML(nsIsolationPath, ns) - err := testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Ready) + err := testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -115,7 +119,7 @@ func TestSecurityPolicyAddDeleteRule(t *testing.T) { nsIsolationPath, _ = filepath.Abs("./manifest/testSecurityPolicy/ns-isolation-policy-1.yaml") _ = applyYAML(nsIsolationPath, ns) defer deleteYAML(nsIsolationPath, ns) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -126,7 +130,7 @@ func TestSecurityPolicyAddDeleteRule(t *testing.T) { // Delete security policy _ = deleteYAML(nsIsolationPath, ns) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -172,7 +176,7 @@ func TestSecurityPolicyMatchExpression(t *testing.T) { nsIsolationPath, _ := filepath.Abs("./manifest/testSecurityPolicy/match-expression.yaml") _ = applyYAML(nsIsolationPath, ns) defer deleteYAML(nsIsolationPath, ns) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -189,7 +193,7 @@ func TestSecurityPolicyMatchExpression(t *testing.T) { // Delete security policy _ = deleteYAML(nsIsolationPath, ns) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, ns, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, ns, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -235,7 +239,7 @@ func TestSecurityPolicyNamedPort0(t *testing.T) { psb, _, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod %s", webA) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -252,7 +256,7 @@ func TestSecurityPolicyNamedPort0(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -292,7 +296,7 @@ func TestSecurityPolicyNamedPort1(t *testing.T) { psb, _, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod %s", webA) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -309,7 +313,7 @@ func TestSecurityPolicyNamedPort1(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -349,7 +353,7 @@ func TestSecurityPolicyNamedPort2(t *testing.T) { psb, _, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod %s", webA) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -371,7 +375,7 @@ func TestSecurityPolicyNamedPort2(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -417,7 +421,7 @@ func TestSecurityPolicyNamedPort3(t *testing.T) { _, psb, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod ns %s", nsWeb) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -432,7 +436,7 @@ func TestSecurityPolicyNamedPort3(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -478,7 +482,7 @@ func TestSecurityPolicyNamedPort4(t *testing.T) { _, psb, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod ns %s", nsWeb) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -493,7 +497,7 @@ func TestSecurityPolicyNamedPort4(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -550,7 +554,7 @@ func TestSecurityPolicyNamedPort5(t *testing.T) { _, psb, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod ns %s", nsWeb) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -569,7 +573,7 @@ func TestSecurityPolicyNamedPort5(t *testing.T) { cmd = fmt.Sprintf("kubectl label ns %s %s=%s --overwrite", nsDB2, "role", "db") _, err = runCommand(cmd) assert_nil(t, err, "Error when running command %s", cmd) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) err = testData.waitForResourceExistOrNot(nsWeb, common.ResourceTypeRule, ruleName1, true) assert_nil(t, err) @@ -582,7 +586,7 @@ func TestSecurityPolicyNamedPort5(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -646,7 +650,7 @@ func TestSecurityPolicyNamedPort6(t *testing.T) { _, psb, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod ns %s", nsWeb) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -665,7 +669,7 @@ func TestSecurityPolicyNamedPort6(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing @@ -729,7 +733,7 @@ func TestSecurityPolicyNamedPort7(t *testing.T) { _, psb, err := testData.deploymentWaitForIPsOrNames(defaultTimeout, nsWeb, labelWeb) t.Logf("Pods are %v", psb) assert_nil(t, err, "Error when waiting for IP for Pod ns %s", nsWeb) - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Ready) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Ready) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource existing @@ -748,7 +752,7 @@ func TestSecurityPolicyNamedPort7(t *testing.T) { // Delete all _ = deleteYAML(podPath, "") - err = testData.waitForSecurityPolicyReadyOrDeleted(defaultTimeout, nsWeb, securityPolicyName, Deleted) + err = testData.waitForCRReadyOrDeleted(defaultTimeout, SP, nsWeb, securityPolicyName, Deleted) assert_nil(t, err, "Error when waiting for Security Policy %s", securityPolicyName) // Check nsx-t resource not existing diff --git a/test/e2e/nsx_vpc_test.go b/test/e2e/nsx_vpc_test.go new file mode 100644 index 000000000..5ef7e8025 --- /dev/null +++ b/test/e2e/nsx_vpc_test.go @@ -0,0 +1,149 @@ +package e2e + +import ( + "log" + "path/filepath" + "strings" + "testing" +) + +const ( + VPCCRType = "vpcs" + VPCNSXType = "Vpc" + PrivateIPBlockNSXType = "IpAddressBlock" + + InfraVPCNamespace = "kube-system" + SharedInfraVPCNamespace = "kube-public" + + DefaultPrivateCIDR1 = "172.28.0.0" + DefaultPrivateCIDR2 = "172.38.0.0" + InfraPrivateCIDR1 = "172.27.0.0" + InfraPrivateCIDR2 = "172.37.0.0" + CustomizedPrivateCIDR1 = "172.29.0.0" + CustomizedPrivateCIDR2 = "172.39.0.0" +) + +var ( + verify_keys = []string{"defaultSNATIP", "lbSubnetCIDR", "lbSubnetPath", "nsxResourcePath"} +) + +func verifyVPCCRCreated(t *testing.T, ns string, expect int) (string, string) { + // there should be one vpc created + resources, err := testData.getCRResource(defaultTimeout, VPCCRType, ns) + // only one vpc should be created under ns using default network config + if len(resources) != expect { + log.Printf("VPC list %s size not the same as expected %d", resources, expect) + panic("VPC CR creation verify failed") + } + assert_nil(t, err) + + var vpc_name, vpc_uid string = "", "" + // waiting for CR to be ready + for k, v := range resources { + vpc_name = k + vpc_uid = strings.TrimSpace(v) + } + + return vpc_name, vpc_uid +} + +func verifyPrivateIPBlockCreated(t *testing.T, ns, id string) { + err := testData.waitForResourceExistById(ns, PrivateIPBlockNSXType, id, true) + assert_nil(t, err) +} + +func verifyVPCCRProperties(t *testing.T, ns, vpc_name string) { + for _, key := range verify_keys { + value, err := testData.getCRProperties(defaultTimeout, VPCCRType, vpc_name, ns, key) + assert_nil(t, err) + if strings.TrimSpace(value) == "" { + log.Printf("failed to read key %s for VPC %s", key, vpc_name) + panic("failed to read attribute from VPC CR") + } + } +} + +// Test Customized VPC +func TestCustomizedVPC(t *testing.T) { + // Create customized networkconfig + ncPath, _ := filepath.Abs("./manifest/testVPC/customize_networkconfig.yaml") + _ = applyYAML(ncPath, "") + nsPath, _ := filepath.Abs("./manifest/testVPC/customize_ns.yaml") + _ = applyYAML(nsPath, "") + + defer deleteYAML(nsPath, "") + defer deleteYAML(ncPath, "") + + ns := "customized-ns" + + vpc_name, vpc_uid := verifyVPCCRCreated(t, ns, 1) + + err := testData.waitForCRReadyOrDeleted(defaultTimeout, VPCCRType, ns, vpc_name, Ready) + assert_nil(t, err, "Error when waiting for VPC %s", vpc_name) + + verifyVPCCRProperties(t, ns, vpc_name) + + // Check nsx-t resource existing, nsx vpc is using vpc cr uid as id + err = testData.waitForResourceExistById(ns, VPCNSXType, vpc_uid, true) + assert_nil(t, err) + + //verify private ipblocks created for vpc + p_ipb_id1 := vpc_uid + "_" + CustomizedPrivateCIDR1 + p_ipb_id2 := vpc_uid + "_" + CustomizedPrivateCIDR2 + + verifyPrivateIPBlockCreated(t, ns, p_ipb_id1) + verifyPrivateIPBlockCreated(t, ns, p_ipb_id2) +} + +// Test Infra VPC +func TestInfraVPC(t *testing.T) { + // there should be one shared vpc created under namespace kube-system + vpc_name, vpc_uid := verifyVPCCRCreated(t, InfraVPCNamespace, 1) + + err := testData.waitForCRReadyOrDeleted(defaultTimeout, VPCCRType, InfraVPCNamespace, vpc_name, Ready) + assert_nil(t, err, "Error when waiting for VPC %s", vpc_name) + + verifyVPCCRProperties(t, InfraVPCNamespace, vpc_name) + + // Check nsx-t resource existing, nsx vpc is using vpc cr uid as id + err = testData.waitForResourceExistById(InfraVPCNamespace, VPCNSXType, vpc_uid, true) + assert_nil(t, err) + + //verify private ipblocks created for vpc + p_ipb_id1 := vpc_uid + "_" + InfraPrivateCIDR1 + p_ipb_id2 := vpc_uid + "_" + InfraPrivateCIDR2 + + verifyPrivateIPBlockCreated(t, InfraVPCNamespace, p_ipb_id1) + verifyPrivateIPBlockCreated(t, InfraVPCNamespace, p_ipb_id2) + + // there should be no VPC exist under namespace kube-public + _, _ = verifyVPCCRCreated(t, SharedInfraVPCNamespace, 0) +} + +// Test Default VPC +func TestDefaultVPC(t *testing.T) { + // If no annotation on namespace, then VPC will use default network config to create + // VPC under each ns + ns := "vpc-default-1" + setupTest(t, ns) + defer teardownTest(t, ns) + + // Check vpc cr existence + vpc_name, vpc_uid := verifyVPCCRCreated(t, ns, 1) + + err := testData.waitForCRReadyOrDeleted(defaultTimeout, VPCCRType, ns, vpc_name, Ready) + assert_nil(t, err, "Error when waiting for VPC %s", vpc_name) + + verifyVPCCRProperties(t, ns, vpc_name) + + // Check nsx-t resource existing, nsx vpc is using vpc cr uid as id + err = testData.waitForResourceExistById(ns, VPCNSXType, vpc_uid, true) + assert_nil(t, err) + + //verify private ipblocks created for vpc + p_ipb_id1 := vpc_uid + "_" + DefaultPrivateCIDR1 + p_ipb_id2 := vpc_uid + "_" + DefaultPrivateCIDR2 + + verifyPrivateIPBlockCreated(t, ns, p_ipb_id1) + verifyPrivateIPBlockCreated(t, ns, p_ipb_id2) +}