Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ spec:
- message: Value is immutable
rule: self == oldSelf
ipAddressBlockVisibility:
default: Private
description: |-
IPAddressBlockVisibility specifies the visibility of the IPBlocks to allocate IP addresses. Can be External, Private or PrivateTGW.
This field is not applicable if ipAddressType is IPv6.
Expand All @@ -93,7 +92,8 @@ spec:
rule: self == oldSelf
ipv6AllocationPrefixLength:
description: IPv6AllocationPrefixLength specifies the prefix length
of IPv6 addresses.
of IPv6 addresses. Defaults to 64 when ipAddressType is IPv6 and
this field is not specified.
maximum: 128
minimum: 64
type: integer
Expand All @@ -120,6 +120,9 @@ spec:
is IPv6
rule: '!has(self.ipv6AllocationPrefixLength) || self.ipAddressType ==
''IPv6'''
- message: ipAddressBlockVisibility cannot be set when ipAddressType is IPv6
rule: '!has(self.ipAddressBlockVisibility) || !has(self.ipAddressType) || self.ipAddressType
!= ''IPv6'''
status:
description: IPAddressAllocationStatus defines the observed state of IPAddressAllocation.
properties:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ require (
github.com/vmware/govmomi v0.53.1
github.com/vmware/vsphere-automation-sdk-go/lib v0.8.0
github.com/vmware/vsphere-automation-sdk-go/runtime v0.8.0
github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.0.0-20260506074423-13747423203f
github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.1-0.20260517061842-508c01aec2fc
github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.0.0-20260506074423-13747423203f
go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ github.com/vmware/vsphere-automation-sdk-go/lib v0.8.0 h1:u1SXOTM6D4Ygb3jeidj2Rd
github.com/vmware/vsphere-automation-sdk-go/lib v0.8.0/go.mod h1:8d5JTwjpM/Z03n/IZb0fwmXkJNWvWwuLXBqoakqYio4=
github.com/vmware/vsphere-automation-sdk-go/runtime v0.8.0 h1:KnDIX9LY0nru7iMQTg0sy9vChhyorPo5OdASM2MaAcI=
github.com/vmware/vsphere-automation-sdk-go/runtime v0.8.0/go.mod h1:DzLetYAmw1+vj7bqElRWEpuy40WYE/woL3alsymYa/c=
github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.0.0-20260506074423-13747423203f h1:HvbZGTOUm9rJDG7ngNQSd5UC5ikiZI/M3cUai8u5+Jg=
github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.0.0-20260506074423-13747423203f/go.mod h1:C3JVOHRVLrGBQ8kTWAiGYlRz5UQC5qAcTdt3tvA+5P0=
github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.1-0.20260517061842-508c01aec2fc h1:eiMYaZj7fQimyNjQkIsjtbr12RKIC5Dl+6uhtPZVtdo=
github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.1-0.20260517061842-508c01aec2fc/go.mod h1:C3JVOHRVLrGBQ8kTWAiGYlRz5UQC5qAcTdt3tvA+5P0=
github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.0.0-20260506074423-13747423203f h1:dzC9XLdl0fdqZB/K97m/NMY9o/voA2Qa4shHRnK900Q=
github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.0.0-20260506074423-13747423203f/go.mod h1:fDH7JI080OD5t6TGwjJx3mMX/g6W7t6Radlome6hze8=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
Expand Down
6 changes: 1 addition & 5 deletions pkg/apis/eas/v1alpha1/ipblock_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient:nonNamespaced
// +kubebuilder:resource:scope=Namespace
Expand All @@ -27,4 +23,4 @@ type IPPoolRange struct {
Start string `json:"start" protobuf:"bytes,1,opt,name=start"`
// The end IP Address of the IP range. format: IP.
End string `json:"end" protobuf:"bytes,2,opt,name=end"`
}
}
3 changes: 2 additions & 1 deletion pkg/apis/vpc/v1alpha1/ipaddressallocation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ type IPAddressAllocationList struct {
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.ipv6AllocationPrefixLength) || has(self.ipv6AllocationPrefixLength)", message="ipv6AllocationPrefixLength is required once set"
// +kubebuilder:validation:XValidation:rule="!has(self.allocationSize) || !has(self.ipAddressType) || self.ipAddressType == 'IPv4'", message="allocationSize can only be set when ipAddressType is IPv4"
// +kubebuilder:validation:XValidation:rule="!has(self.ipv6AllocationPrefixLength) || self.ipAddressType == 'IPv6'", message="ipv6AllocationPrefixLength can only be set when ipAddressType is IPv6"
// +kubebuilder:validation:XValidation:rule="!has(self.ipAddressBlockVisibility) || !has(self.ipAddressType) || self.ipAddressType != 'IPv6'", message="ipAddressBlockVisibility cannot be set when ipAddressType is IPv6"
Comment thread
poojav25 marked this conversation as resolved.
type IPAddressAllocationSpec struct {
// IPAddressBlockVisibility specifies the visibility of the IPBlocks to allocate IP addresses. Can be External, Private or PrivateTGW.
// This field is not applicable if ipAddressType is IPv6.
// +kubebuilder:validation:Enum=External;Private;PrivateTGW
// +kubebuilder:default=Private
// +optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
IPAddressBlockVisibility IPAddressVisibility `json:"ipAddressBlockVisibility,omitempty"`
Expand All @@ -67,6 +67,7 @@ type IPAddressAllocationSpec struct {
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
AllocationIPs string `json:"allocationIPs,omitempty"`
// IPv6AllocationPrefixLength specifies the prefix length of IPv6 addresses.
// Defaults to 64 when ipAddressType is IPv6 and this field is not specified.
// +kubebuilder:validation:Minimum:=64
// +kubebuilder:validation:Maximum:=128
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
Expand Down
22 changes: 16 additions & 6 deletions pkg/controllers/ipaddressallocation/ipaddressallocation_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,23 @@ func (v *IPAddressAllocationValidator) Handle(ctx context.Context, req admission
return admission.Errored(http.StatusBadRequest, err)
}
if req.Operation != admissionv1.Delete {
err := common.CheckAccessModeOrVisibility(v.Client, ctx, ipAddressAllocation.Namespace, string(ipAddressAllocation.Spec.IPAddressBlockVisibility), servicecommon.ResourceTypeIPAddressAllocation)
if err != nil {
log.Error(err, "IPAddressVisibility not supported", "IPAddressVisibility", ipAddressAllocation.Spec.IPAddressBlockVisibility, "namespace", ipAddressAllocation.Namespace)
if errors.Is(err, common.ErrFailedToListNetworkInfo) {
return admission.Errored(http.StatusServiceUnavailable, err)
// For IPv6 allocations, ipAddressBlockVisibility is not applicable and should be omitted.
// For IPv4 allocations, if ipAddressBlockVisibility is omitted, default to Private for validation.
visibility := string(ipAddressAllocation.Spec.IPAddressBlockVisibility)
if ipAddressAllocation.Spec.IPAddressType == v1alpha1.IPAllocationIPAddressTypeIPv6 {
visibility = ""
} else if visibility == "" {
visibility = string(v1alpha1.IPAddressVisibilityPrivate)
}
if visibility != "" {
err := common.CheckAccessModeOrVisibility(v.Client, ctx, ipAddressAllocation.Namespace, visibility, servicecommon.ResourceTypeIPAddressAllocation)
if err != nil {
log.Error(err, "IPAddressVisibility not supported", "IPAddressVisibility", visibility, "namespace", ipAddressAllocation.Namespace)
if errors.Is(err, common.ErrFailedToListNetworkInfo) {
return admission.Errored(http.StatusServiceUnavailable, err)
}
return admission.Denied(err.Error())
}
Comment thread
poojav25 marked this conversation as resolved.
return admission.Denied(err.Error())
}
}
switch req.Operation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@ func TestIPAddressAllocationValidator_Handle(t *testing.T) {
AllocationIPs: "10.0.0.10",
},
})
reqCreateIPv6, _ := json.Marshal(&v1alpha1.IPAddressAllocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns1",
Name: "ip-ipv6",
},
Spec: v1alpha1.IPAddressAllocationSpec{
IPAddressType: v1alpha1.IPAllocationIPAddressTypeIPv6,
IPv6AllocationPrefixLength: 64,
},
})
reqCreateIPv4NoVis, _ := json.Marshal(&v1alpha1.IPAddressAllocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns1",
Name: "ip-ipv4-novis",
},
Spec: v1alpha1.IPAddressAllocationSpec{
AllocationSize: 16,
},
})
type args struct {
req admission.Request
}
Expand Down Expand Up @@ -181,6 +200,39 @@ func TestIPAddressAllocationValidator_Handle(t *testing.T) {
}}},
want: admission.Allowed(""),
},
{
name: "create IPv6 - visibility check skipped",
prepareFunc: func(t *testing.T, k8sClient client.Client, ctx context.Context) *gomonkey.Patches {
// CheckAccessModeOrVisibility returns an error, but for IPv6 it must never be called.
patches := gomonkey.ApplyFunc(common.CheckAccessModeOrVisibility, func(_ client.Client, ctx context.Context, ns string, accessMode string, resourceType string) error {
t.Errorf("CheckAccessModeOrVisibility must not be called for IPv6 allocations")
return errors.New("unexpected call")
})
return patches
},
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
Operation: admissionv1.Create,
Object: runtime.RawExtension{Raw: reqCreateIPv6},
}}},
want: admission.Allowed(""),
},
{
name: "create IPv4 no visibility - defaults to Private",
prepareFunc: func(t *testing.T, k8sClient client.Client, ctx context.Context) *gomonkey.Patches {
patches := gomonkey.ApplyFunc(common.CheckAccessModeOrVisibility, func(_ client.Client, ctx context.Context, ns string, accessMode string, resourceType string) error {
if accessMode != string(v1alpha1.IPAddressVisibilityPrivate) {
t.Errorf("expected accessMode %q, got %q", v1alpha1.IPAddressVisibilityPrivate, accessMode)
}
return nil
})
return patches
},
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
Operation: admissionv1.Create,
Object: runtime.RawExtension{Raw: reqCreateIPv4NoVis},
}}},
want: admission.Allowed(""),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ func (r *Reconciler) setNotSupported(ctx context.Context, req ctrl.Request) erro
return nil
}

// +kubebuilder:rbac:groups=crd.nsx.vmware.com,resources=subnetipreservation,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=crd.nsx.vmware.com,resources=subnetipreservation/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=crd.nsx.vmware.com,resources=subnetipreservations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=crd.nsx.vmware.com,resources=subnetipreservations/status,verbs=get;update;patch
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
startTime := time.Now()
defer func() {
Expand Down
17 changes: 14 additions & 3 deletions pkg/controllers/subnetport/addressbinding_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,23 @@ func (v *AddressBindingValidator) Handle(ctx context.Context, req admission.Requ
log.Error(err, "failed to get IPAddressAllocation", "IPAddressAllocation", ab.Namespace+"/"+ab.Spec.IPAddressAllocationName)
return admission.Denied(fmt.Sprintf("IPAddressAllocation %s does not exist", ab.Spec.IPAddressAllocationName))
}
if ipAllocation.Spec.IPAddressBlockVisibility != v1alpha1.IPAddressVisibilityExternal {
return admission.Denied("IPBlock visibility of IPAddressAllocation must be \"External\"")
if ipAllocation.Spec.IPAddressType != v1alpha1.IPAllocationIPAddressTypeIPv6 {
if ipAllocation.Spec.IPAddressBlockVisibility != v1alpha1.IPAddressVisibilityExternal {
return admission.Denied("IPBlock visibility of IPAddressAllocation must be \"External\"")
}
}
if (ipAllocation.Spec.AllocationIPs != "" && net.ParseIP(ipAllocation.Spec.AllocationIPs) == nil) || (ipAllocation.Spec.AllocationIPs == "" && ipAllocation.Spec.AllocationSize != 1) {
if ipAllocation.Spec.AllocationIPs != "" && net.ParseIP(ipAllocation.Spec.AllocationIPs) == nil {
return admission.Denied("IPAddressAllocation must be a single IP")
}
if ipAllocation.Spec.AllocationIPs == "" {
if ipAllocation.Spec.IPAddressType == v1alpha1.IPAllocationIPAddressTypeIPv6 {
if ipAllocation.Spec.IPv6AllocationPrefixLength != 128 {
return admission.Denied("IPAddressAllocation must be a single IP")
}
} else if ipAllocation.Spec.AllocationSize != 1 {
return admission.Denied("IPAddressAllocation must be a single IP")
}
}
}
return admission.Allowed("")
}
98 changes: 98 additions & 0 deletions pkg/controllers/subnetport/addressbinding_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,104 @@ func TestAddressBindingValidator_Handle(t *testing.T) {
},
want: admission.Denied("IPAddressAllocation must be a single IP"),
},
{
name: "create with IPv6 allocation and prefix /128 (single IP)",
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Create, Object: runtime.RawExtension{Raw: req3}}}},
prepareFunc: func(t *testing.T, c client.Client, v *AddressBindingValidator, ctx context.Context) *gomonkey.Patches {
patches := gomonkey.ApplyFunc(common.CheckNetworkStack,
func(_ client.Client, _ context.Context, _ string, _ string) error {
return nil
})
patches.ApplyMethodSeq(c, "List", []gomonkey.OutputCell{{
Values: gomonkey.Params{nil},
Times: 1,
}})
c.Create(context.TODO(), &v1alpha1.IPAddressAllocation{
TypeMeta: v1.TypeMeta{},
ObjectMeta: v1.ObjectMeta{Namespace: "ns1", Name: "ip1"},
Spec: v1alpha1.IPAddressAllocationSpec{
IPAddressBlockVisibility: v1alpha1.IPAddressVisibilityExternal,
IPAddressType: v1alpha1.IPAllocationIPAddressTypeIPv6,
IPv6AllocationPrefixLength: 128,
},
})
return patches
},
want: admission.Allowed(""),
},
{
name: "create with IPv6 allocation and prefix /64 (not a single IP)",
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Create, Object: runtime.RawExtension{Raw: req3}}}},
prepareFunc: func(t *testing.T, c client.Client, v *AddressBindingValidator, ctx context.Context) *gomonkey.Patches {
patches := gomonkey.ApplyFunc(common.CheckNetworkStack,
func(_ client.Client, _ context.Context, _ string, _ string) error {
return nil
})
patches.ApplyMethodSeq(c, "List", []gomonkey.OutputCell{{
Values: gomonkey.Params{nil},
Times: 1,
}})
c.Create(context.TODO(), &v1alpha1.IPAddressAllocation{
TypeMeta: v1.TypeMeta{},
ObjectMeta: v1.ObjectMeta{Namespace: "ns1", Name: "ip1"},
Spec: v1alpha1.IPAddressAllocationSpec{
IPAddressBlockVisibility: v1alpha1.IPAddressVisibilityExternal,
IPAddressType: v1alpha1.IPAllocationIPAddressTypeIPv6,
IPv6AllocationPrefixLength: 64,
},
})
return patches
},
want: admission.Denied("IPAddressAllocation must be a single IP"),
},
{
name: "create with IPv6 allocation /128 no visibility set (single IP)",
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Create, Object: runtime.RawExtension{Raw: req3}}}},
prepareFunc: func(t *testing.T, c client.Client, v *AddressBindingValidator, ctx context.Context) *gomonkey.Patches {
patches := gomonkey.ApplyFunc(common.CheckNetworkStack,
func(_ client.Client, _ context.Context, _ string, _ string) error {
return nil
})
patches.ApplyMethodSeq(c, "List", []gomonkey.OutputCell{{
Values: gomonkey.Params{nil},
Times: 1,
}})
c.Create(context.TODO(), &v1alpha1.IPAddressAllocation{
TypeMeta: v1.TypeMeta{},
ObjectMeta: v1.ObjectMeta{Namespace: "ns1", Name: "ip1"},
Spec: v1alpha1.IPAddressAllocationSpec{
IPAddressType: v1alpha1.IPAllocationIPAddressTypeIPv6,
IPv6AllocationPrefixLength: 128,
},
})
return patches
},
want: admission.Allowed(""),
},
{
name: "create with IPv6 allocation /64 no visibility set (not a single IP)",
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Create, Object: runtime.RawExtension{Raw: req3}}}},
prepareFunc: func(t *testing.T, c client.Client, v *AddressBindingValidator, ctx context.Context) *gomonkey.Patches {
patches := gomonkey.ApplyFunc(common.CheckNetworkStack,
func(_ client.Client, _ context.Context, _ string, _ string) error {
return nil
})
patches.ApplyMethodSeq(c, "List", []gomonkey.OutputCell{{
Values: gomonkey.Params{nil},
Times: 1,
}})
c.Create(context.TODO(), &v1alpha1.IPAddressAllocation{
TypeMeta: v1.TypeMeta{},
ObjectMeta: v1.ObjectMeta{Namespace: "ns1", Name: "ip1"},
Spec: v1alpha1.IPAddressAllocationSpec{
IPAddressType: v1alpha1.IPAllocationIPAddressTypeIPv6,
IPv6AllocationPrefixLength: 64,
},
})
return patches
},
want: admission.Denied("IPAddressAllocation must be a single IP"),
},
{
name: "create with networkStack VLANBackedVPC",
args: args{req: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Create, Object: runtime.RawExtension{Raw: req3}}}},
Expand Down
Loading
Loading