From 49dab70a6d4ade4fb152afe8ec34b42a1077be61 Mon Sep 17 00:00:00 2001 From: Bryan Venteicher Date: Wed, 31 Dec 2025 14:12:44 -0600 Subject: [PATCH] Validate Network API Group and Kind based on network provider Generally these will be left empty so the VM mutation webhook will default in the correct values, but also validate upfront to what is supported based on the configured network provider type. --- .../validation/virtualmachine_validator.go | 71 +++++- .../virtualmachine_validator_unit_test.go | 212 ++++++++++++++++++ 2 files changed, 278 insertions(+), 5 deletions(-) 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",