diff --git a/webhooks/virtualmachine/validation/virtualmachine_validator.go b/webhooks/virtualmachine/validation/virtualmachine_validator.go index 6a2479618..ba925e2f6 100644 --- a/webhooks/virtualmachine/validation/virtualmachine_validator.go +++ b/webhooks/virtualmachine/validation/virtualmachine_validator.go @@ -33,10 +33,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + netopv1alpha1 "github.com/vmware-tanzu/net-operator-api/api/v1alpha1" vpcv1alpha1 "github.com/vmware-tanzu/nsx-operator/pkg/apis/vpc/v1alpha1" vmopv1 "github.com/vmware-tanzu/vm-operator/api/v1alpha5" "github.com/vmware-tanzu/vm-operator/api/v1alpha5/sysprep" + ncpv1alpha1 "github.com/vmware-tanzu/vm-operator/external/ncp/api/v1alpha1" "github.com/vmware-tanzu/vm-operator/pkg/builder" pkgcfg "github.com/vmware-tanzu/vm-operator/pkg/config" pkgconst "github.com/vmware-tanzu/vm-operator/pkg/constants" @@ -780,7 +782,7 @@ func (v validator) validateNetwork( p := networkPath.Child("interfaces") for i, interfaceSpec := range networkSpec.Interfaces { - allErrs = append(allErrs, v.validateNetworkInterfaceSpec(p.Index(i), interfaceSpec, vm.Name)...) + allErrs = append(allErrs, v.validateNetworkInterfaceSpec(ctx, p.Index(i), interfaceSpec, vm.Name)...) allErrs = append(allErrs, v.validateNetworkInterfaceSpecWithBootstrap(ctx, p.Index(i), interfaceSpec, vm)...) } } @@ -849,20 +851,77 @@ var macAddressSupportNetworkGroups = []string{ vpcv1alpha1.GroupVersion.Group, } +type networkProviderValidation struct { + group string + kinds []string +} + +var networkProviderValidations = map[pkgcfg.NetworkProviderType]networkProviderValidation{ + pkgcfg.NetworkProviderTypeNSXT: { + group: ncpv1alpha1.SchemeGroupVersion.Group, + kinds: []string{"VirtualNetwork"}, + }, + pkgcfg.NetworkProviderTypeVDS: { + group: netopv1alpha1.SchemeGroupVersion.Group, + kinds: []string{"Network"}, + }, + pkgcfg.NetworkProviderTypeVPC: { + group: vpcv1alpha1.GroupVersion.Group, + kinds: []string{"Subnet", "SubnetSet"}, + }, +} + +func (v validator) validateNetworkInterfaceNetworkRef( + ctx *pkgctx.WebhookRequestContext, + interfacePath *field.Path, + networkGV schema.GroupVersion, + networkKind string) field.ErrorList { + + var allErrs field.ErrorList + + providerType := pkgcfg.FromContext(ctx).NetworkProviderType + supported, ok := networkProviderValidations[providerType] + if !ok { + // No supported for this provider type (e.g., Named network provider). + return allErrs + } + + if networkGV.Group != "" && networkGV.Group != supported.group { + allErrs = append(allErrs, field.NotSupported( + interfacePath.Child("network", "apiVersion"), + networkGV.Group, + []string{supported.group})) + } + + if networkKind != "" && !slices.Contains(supported.kinds, networkKind) { + allErrs = append(allErrs, field.NotSupported( + interfacePath.Child("network", "kind"), + networkKind, + supported.kinds)) + } + + return allErrs +} + //nolint:gocyclo func (v validator) validateNetworkInterfaceSpec( + ctx *pkgctx.WebhookRequestContext, interfacePath *field.Path, interfaceSpec vmopv1.VirtualMachineNetworkInterfaceSpec, vmName string) field.ErrorList { - var allErrs field.ErrorList - var networkIfCRName string - var networkAPIVersion string - var networkName string + var ( + allErrs field.ErrorList + networkIfCRName string + networkAPIVersion string + networkName string + networkKind string + ) if interfaceSpec.Network != nil { networkAPIVersion = interfaceSpec.Network.APIVersion networkName = interfaceSpec.Network.Name + networkKind = interfaceSpec.Network.Kind } var networkGV schema.GroupVersion @@ -875,6 +934,8 @@ func (v validator) validateNetworkInterfaceSpec( } } + allErrs = append(allErrs, v.validateNetworkInterfaceNetworkRef(ctx, interfacePath, networkGV, networkKind)...) + // The networkInterface CR name ("vmName-networkName-interfaceName" or "vmName-interfaceName") needs to be a DNS1123 Label if networkName != "" { networkIfCRName = fmt.Sprintf("%s-%s-%s", vmName, networkName, interfaceSpec.Name) diff --git a/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go b/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go index 90593d386..35a9af9ce 100644 --- a/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go +++ b/webhooks/virtualmachine/validation/virtualmachine_validator_unit_test.go @@ -2820,6 +2820,218 @@ func unitTestsValidateCreate() { }, ), ) + + DescribeTable("network create - validate network provider API group and kind", doTest, + + Entry("allow VirtualNetwork kind with NSXT provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeNSXT + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "vmware.com/v1alpha1", + Kind: "VirtualNetwork", + }, + Name: "my-network", + }, + }, + }, + } + }, + expectAllowed: true, + }, + ), + Entry("disallow incorrect API group and kind with NSXT provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeNSXT + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "crd.nsx.vmware.com/v1alpha1", + Kind: "VirtualNetwork", + }, + Name: "my-network", + }, + }, + { + Name: "eth1", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "vmware.com/v1alpha1", + Kind: "SubnetSet", + }, + Name: "my-network-2", + }, + }, + }, + } + }, + validate: doValidateWithMsg( + `spec.network.interfaces[0].network.apiVersion: Unsupported value: "crd.nsx.vmware.com": supported values: "vmware.com"`, + `spec.network.interfaces[1].network.kind: Unsupported value: "SubnetSet": supported values: "VirtualNetwork"`), + }, + ), + + Entry("allow Network kind with VDS provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeVDS + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "netoperator.vmware.com/v1alpha1", + Kind: "Network", + }, + Name: "my-network", + }, + }, + }, + } + }, + expectAllowed: true, + }, + ), + Entry("disallow incorrect API group and kind with VDS provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeVDS + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "vmware.com/v1alpha1", + Kind: "Network", + }, + Name: "my-network", + }, + }, + { + Name: "eth1", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "netoperator.vmware.com/v1alpha1", + Kind: "VirtualNetwork", + }, + Name: "my-network-2", + }, + }, + }, + } + }, + validate: doValidateWithMsg( + `spec.network.interfaces[0].network.apiVersion: Unsupported value: "vmware.com": supported values: "netoperator.vmware.com"`, + `spec.network.interfaces[1].network.kind: Unsupported value: "VirtualNetwork": supported values: "Network"`), + }, + ), + + Entry("allow SubnetSet kind with VPC provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeVPC + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "crd.nsx.vmware.com/v1alpha1", + Kind: "SubnetSet", + }, + Name: "my-network", + }, + }, + }, + } + }, + expectAllowed: true, + }, + ), + Entry("allow Subnet kind with VPC provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeVPC + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "crd.nsx.vmware.com/v1alpha1", + Kind: "Subnet", + }, + Name: "my-network", + }, + }, + }, + } + }, + expectAllowed: true, + }, + ), + + Entry("disallow incorrect API group and kind with VPC provider", + testParams{ + setup: func(ctx *unitValidatingWebhookContext) { + pkgcfg.SetContext(ctx, func(config *pkgcfg.Config) { + config.NetworkProviderType = pkgcfg.NetworkProviderTypeVPC + }) + ctx.vm.Spec.Network = &vmopv1.VirtualMachineNetworkSpec{ + Interfaces: []vmopv1.VirtualMachineNetworkInterfaceSpec{ + { + Name: "eth0", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "vmware.com/v1alpha1", + Kind: "SubnetSet", + }, + Name: "my-network", + }, + }, + { + Name: "eth1", + Network: &common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "crd.nsx.vmware.com/v1alpha1", + Kind: "VirtualNetwork", + }, + Name: "my-network-2", + }, + }, + }, + } + }, + validate: doValidateWithMsg( + `spec.network.interfaces[0].network.apiVersion: Unsupported value: "vmware.com": supported values: "crd.nsx.vmware.com"`, + `spec.network.interfaces[1].network.kind: Unsupported value: "VirtualNetwork": supported values: "Subnet", "SubnetSet"`), + }, + ), + ) + DescribeTable("network create - host and domain names", doTest, Entry("allow simple host name",