diff --git a/api/v1/ipaddress_types.go b/api/v1/ipaddress_types.go index bbab8fdd..66561486 100644 --- a/api/v1/ipaddress_types.go +++ b/api/v1/ipaddress_types.go @@ -28,7 +28,7 @@ type IpAddressSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - //+kubebuilder:validation:Pattern=`^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$` + //+kubebuilder:validation:Format=cidr //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'ipAddress' is immutable" //+kubebuilder:validation:Required IpAddress string `json:"ipAddress"` diff --git a/api/v1/ipaddressclaim_types.go b/api/v1/ipaddressclaim_types.go index 7f02cef6..50a41fb0 100644 --- a/api/v1/ipaddressclaim_types.go +++ b/api/v1/ipaddressclaim_types.go @@ -29,7 +29,7 @@ type IpAddressClaimSpec struct { // Important: Run "make" to regenerate code after modifying this file //+kubebuilder:validation:Required - //+kubebuilder:validation:Pattern=`^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$` + //+kubebuilder:validation:Format=cidr //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'parentPrefix' is immutable" ParentPrefix string `json:"parentPrefix"` diff --git a/api/v1/prefix_types.go b/api/v1/prefix_types.go index 113083a8..f13f0e27 100644 --- a/api/v1/prefix_types.go +++ b/api/v1/prefix_types.go @@ -29,7 +29,7 @@ type PrefixSpec struct { // Important: Run "make" to regenerate code after modifying this file //+kubebuilder:validation:Required - //+kubebuilder:validation:Pattern=`^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$` + //+kubebuilder:validation:Format=cidr //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'prefix' is immutable" Prefix string `json:"prefix"` diff --git a/api/v1/prefixclaim_types.go b/api/v1/prefixclaim_types.go index e693cb5a..91652b43 100644 --- a/api/v1/prefixclaim_types.go +++ b/api/v1/prefixclaim_types.go @@ -29,12 +29,12 @@ type PrefixClaimSpec struct { // Important: Run "make" to regenerate code after modifying this file //+kubebuilder:validation:Required - //+kubebuilder:validation:Pattern=`^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$` + //+kubebuilder:validation:Format=cidr //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'parentPrefix' is immutable" ParentPrefix string `json:"parentPrefix"` //+kubebuilder:validation:Required - //+kubebuilder:validation:Pattern=`^\/([1-9]|[12][0-9]|3[0-2])$` + //+kubebuilder:validation:Pattern=`^\/[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]$` //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'prefixLength' is immutable" PrefixLength string `json:"prefixLength"` diff --git a/config/crd/bases/netbox.dev_ipaddressclaims.yaml b/config/crd/bases/netbox.dev_ipaddressclaims.yaml index 3a106f2a..36606f32 100644 --- a/config/crd/bases/netbox.dev_ipaddressclaims.yaml +++ b/config/crd/bases/netbox.dev_ipaddressclaims.yaml @@ -60,7 +60,7 @@ spec: description: type: string parentPrefix: - pattern: ^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$ + format: cidr type: string x-kubernetes-validations: - message: Field 'parentPrefix' is immutable diff --git a/config/crd/bases/netbox.dev_ipaddresses.yaml b/config/crd/bases/netbox.dev_ipaddresses.yaml index f173c638..7bac05a2 100644 --- a/config/crd/bases/netbox.dev_ipaddresses.yaml +++ b/config/crd/bases/netbox.dev_ipaddresses.yaml @@ -63,7 +63,7 @@ spec: description: type: string ipAddress: - pattern: ^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$ + format: cidr type: string x-kubernetes-validations: - message: Field 'ipAddress' is immutable diff --git a/config/crd/bases/netbox.dev_prefixclaims.yaml b/config/crd/bases/netbox.dev_prefixclaims.yaml index d993cec2..8f85bebb 100644 --- a/config/crd/bases/netbox.dev_prefixclaims.yaml +++ b/config/crd/bases/netbox.dev_prefixclaims.yaml @@ -60,13 +60,13 @@ spec: description: type: string parentPrefix: - pattern: ^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$ + format: cidr type: string x-kubernetes-validations: - message: Field 'parentPrefix' is immutable rule: self == oldSelf prefixLength: - pattern: ^\/([1-9]|[12][0-9]|3[0-2])$ + pattern: ^\/[0-9]|[1-9][0-9]|1[01][0-9]|12[0-8]$ type: string x-kubernetes-validations: - message: Field 'prefixLength' is immutable diff --git a/config/crd/bases/netbox.dev_prefixes.yaml b/config/crd/bases/netbox.dev_prefixes.yaml index 377e1a0e..c446875c 100644 --- a/config/crd/bases/netbox.dev_prefixes.yaml +++ b/config/crd/bases/netbox.dev_prefixes.yaml @@ -63,7 +63,7 @@ spec: description: type: string prefix: - pattern: ^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\/([1-9]|[12][0-9]|3[0-2])$ + format: cidr type: string x-kubernetes-validations: - message: Field 'prefix' is immutable diff --git a/internal/controller/ipaddress_controller.go b/internal/controller/ipaddress_controller.go index b5102513..ad51a8cc 100644 --- a/internal/controller/ipaddress_controller.go +++ b/internal/controller/ipaddress_controller.go @@ -118,9 +118,10 @@ func (r *IpAddressReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // get name of parent prefix - parentPrefixName := strings.Replace(ipAddressClaim.Spec.ParentPrefix, "/", "-", -1) - - leaseLockerNSN := types.NamespacedName{Name: parentPrefixName, Namespace: r.OperatorNamespace} + leaseLockerNSN := types.NamespacedName{ + Name: convertCIDRToLeaseLockName(ipAddressClaim.Spec.ParentPrefix), + Namespace: r.OperatorNamespace, + } ll, err = leaselocker.NewLeaseLocker(r.RestConfig, leaseLockerNSN, req.NamespacedName.String()) if err != nil { return ctrl.Result{}, err @@ -132,13 +133,14 @@ func (r *IpAddressReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // create lock locked := ll.TryLock(lockCtx) if !locked { - logger.Info(fmt.Sprintf("failed to lock parent prefix %s", parentPrefixName)) - r.Recorder.Eventf(o, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", parentPrefixName) + logger.Info(fmt.Sprintf("failed to lock parent prefix %s", ipAddressClaim.Spec.ParentPrefix)) + r.Recorder.Eventf(o, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", + ipAddressClaim.Spec.ParentPrefix) return ctrl.Result{ RequeueAfter: 2 * time.Second, }, nil } - debugLogger.Info(fmt.Sprintf("sucessfully locked parent prefix %s", parentPrefixName)) + debugLogger.Info(fmt.Sprintf("successfully locked parent prefix %s", ipAddressClaim.Spec.ParentPrefix)) } // 2. reserve or update ip address in netbox diff --git a/internal/controller/ipaddressclaim_controller.go b/internal/controller/ipaddressclaim_controller.go index bf88be31..18164d37 100644 --- a/internal/controller/ipaddressclaim_controller.go +++ b/internal/controller/ipaddressclaim_controller.go @@ -95,9 +95,10 @@ func (r *IpAddressClaimReconciler) Reconcile(ctx context.Context, req ctrl.Reque debugLogger.Info("ipaddress object matching ipaddress claim was not found, creating new ipaddress object") // 2. check if lease for parent prefix is available - parentPrefixName := strings.ReplaceAll(o.Spec.ParentPrefix, "/", "-") - - leaseLockerNSN := types.NamespacedName{Name: parentPrefixName, Namespace: r.OperatorNamespace} + leaseLockerNSN := types.NamespacedName{ + Name: convertCIDRToLeaseLockName(o.Spec.ParentPrefix), + Namespace: r.OperatorNamespace, + } ll, err := leaselocker.NewLeaseLocker(r.RestConfig, leaseLockerNSN, req.Namespace+"/"+ipAddressName) if err != nil { return ctrl.Result{}, err @@ -110,21 +111,24 @@ func (r *IpAddressClaimReconciler) Reconcile(ctx context.Context, req ctrl.Reque locked := ll.TryLock(lockCtx) if !locked { // lock for parent prefix was not available, rescheduling - logger.Info(fmt.Sprintf("failed to lock parent prefix %s", parentPrefixName)) - r.Recorder.Eventf(o, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", parentPrefixName) + logger.Info(fmt.Sprintf("failed to lock parent prefix %s", o.Spec.ParentPrefix)) + r.Recorder.Eventf(o, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", + o.Spec.ParentPrefix) return ctrl.Result{ RequeueAfter: 2 * time.Second, }, nil } - debugLogger.Info(fmt.Sprintf("successfully locked parent prefix %s", parentPrefixName)) + debugLogger.Info(fmt.Sprintf("successfully locked parent prefix %s", o.Spec.ParentPrefix)) // 4. try to reclaim ip address h := generateIpAddressRestorationHash(o) ipAddressModel, err := r.NetboxClient.RestoreExistingIpByHash(config.GetOperatorConfig().NetboxRestorationHashFieldName, h) if err != nil { - return ctrl.Result{}, err + if setConditionErr := r.SetConditionAndCreateEvent(ctx, o, netboxv1.ConditionIpAssignedFalse, corev1.EventTypeWarning, err.Error()); setConditionErr != nil { + return ctrl.Result{}, fmt.Errorf("error updating status: %w, looking up ip by hash failed: %w", setConditionErr, err) + } + return ctrl.Result{Requeue: true}, nil } - // TODO: set condition for each error if ipAddressModel == nil { // ip address cannot be restored from netbox @@ -137,11 +141,9 @@ func (r *IpAddressClaimReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, }) if err != nil { - setConditionErr := r.SetConditionAndCreateEvent(ctx, o, netboxv1.ConditionIpAssignedFalse, corev1.EventTypeWarning, err.Error()) - if setConditionErr != nil { - return ctrl.Result{}, fmt.Errorf("error updating status: %w, when getting an available IP address failed: %w", setConditionErr, err) + if setConditionErr := r.SetConditionAndCreateEvent(ctx, o, netboxv1.ConditionIpAssignedFalse, corev1.EventTypeWarning, err.Error()); setConditionErr != nil { + return ctrl.Result{}, fmt.Errorf("error updating status: %w, when assignment of ip address failed: %w", setConditionErr, err) } - return ctrl.Result{Requeue: true}, nil } debugLogger.Info(fmt.Sprintf("ip address is not reserved in netbox, assigned new ip address: %s", ipAddressModel.IpAddress)) diff --git a/internal/controller/netbox_testdata_test.go b/internal/controller/netbox_testdata_test.go index 83a11704..b79570d3 100644 --- a/internal/controller/netbox_testdata_test.go +++ b/internal/controller/netbox_testdata_test.go @@ -24,6 +24,7 @@ import ( "github.com/netbox-community/go-netbox/v3/netbox/client/tenancy" netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" netboxv1 "github.com/netbox-community/netbox-operator/api/v1" + "github.com/netbox-community/netbox-operator/pkg/netbox/api" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -40,6 +41,7 @@ var comments = "integration test comment" var siteSlug = "mars-ip-claim" var ipAddress = "1.0.0.1/32" +var ipAddressFamily = int64(api.IPv4Family) var parentPrefix = "1.0.0.0/28" var siteId = int64(2) @@ -136,6 +138,7 @@ func mockedResponseExpectedAvailableIpAddress() []*netboxModels.AvailableIP { return []*netboxModels.AvailableIP{ { Address: ipAddress, + Family: ipAddressFamily, }, } } diff --git a/internal/controller/prefix_controller.go b/internal/controller/prefix_controller.go index d77e9f41..7837d859 100644 --- a/internal/controller/prefix_controller.go +++ b/internal/controller/prefix_controller.go @@ -119,9 +119,10 @@ func (r *PrefixReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } // get the name of the parent prefix - parentPrefixName := strings.Replace(prefixClaim.Spec.ParentPrefix, "/", "-", -1) - - leaseLockerNSN := types.NamespacedName{Name: parentPrefixName, Namespace: r.OperatorNamespace} + leaseLockerNSN := types.NamespacedName{ + Name: convertCIDRToLeaseLockName(prefixClaim.Spec.ParentPrefix), + Namespace: r.OperatorNamespace, + } ll, err = leaselocker.NewLeaseLocker(r.RestConfig, leaseLockerNSN, req.NamespacedName.String()) if err != nil { return ctrl.Result{}, err @@ -132,13 +133,14 @@ func (r *PrefixReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr // create lock if locked := ll.TryLock(lockCtx); !locked { - logger.Info(fmt.Sprintf("failed to lock parent prefix %s", parentPrefixName)) - r.Recorder.Eventf(prefix, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", parentPrefixName) + logger.Info(fmt.Sprintf("failed to lock parent prefix %s", prefixClaim.Spec.ParentPrefix)) + r.Recorder.Eventf(prefix, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", + prefixClaim.Spec.ParentPrefix) return ctrl.Result{ RequeueAfter: 2 * time.Second, }, nil } - debugLogger.Info("sucessfully locked parent prefix %s", parentPrefixName) + debugLogger.Info("successfully locked parent prefix %s", prefixClaim.Spec.ParentPrefix) } /* 2. reserve or update Prefix in netbox */ diff --git a/internal/controller/prefixclaim_controller.go b/internal/controller/prefixclaim_controller.go index 4eb3542c..9d4dd98f 100644 --- a/internal/controller/prefixclaim_controller.go +++ b/internal/controller/prefixclaim_controller.go @@ -19,7 +19,6 @@ package controller import ( "context" "fmt" - "strings" "time" "github.com/netbox-community/netbox-operator/pkg/netbox/models" @@ -91,8 +90,10 @@ func (r *PrefixClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) debugLogger.Info("the prefix was not found, will create a new prefix object now") /* 2. check if the lease for parent prefix is available */ - parentPrefixName := strings.ReplaceAll(prefixClaim.Spec.ParentPrefix, "/", "-") - leaseLockerNSN := types.NamespacedName{Name: parentPrefixName, Namespace: r.OperatorNamespace} + leaseLockerNSN := types.NamespacedName{ + Name: convertCIDRToLeaseLockName(prefixClaim.Spec.ParentPrefix), + Namespace: r.OperatorNamespace, + } ll, err := leaselocker.NewLeaseLocker(r.RestConfig, leaseLockerNSN, req.Namespace+"/"+prefixName) if err != nil { return ctrl.Result{}, err @@ -105,21 +106,24 @@ func (r *PrefixClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) locked := ll.TryLock(lockCtx) if !locked { // lock for parent prefix was not available, rescheduling - logger.Info(fmt.Sprintf("failed to lock parent prefix %s", parentPrefixName)) - r.Recorder.Eventf(prefixClaim, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", parentPrefixName) + logger.Info(fmt.Sprintf("failed to lock parent prefix %s", prefixClaim.Spec.ParentPrefix)) + r.Recorder.Eventf(prefixClaim, corev1.EventTypeWarning, "FailedToLockParentPrefix", "failed to lock parent prefix %s", + prefixClaim.Spec.ParentPrefix) return ctrl.Result{ RequeueAfter: 2 * time.Second, }, nil } - debugLogger.Info(fmt.Sprintf("successfully locked parent prefix %s", parentPrefixName)) + debugLogger.Info(fmt.Sprintf("successfully locked parent prefix %s", prefixClaim.Spec.ParentPrefix)) // 4. try to reclaim Prefix using restorationHash h := generatePrefixRestorationHash(prefixClaim) prefixModel, err := r.NetboxClient.RestoreExistingPrefixByHash(h) if err != nil { - return ctrl.Result{}, err + if setConditionErr := r.SetConditionAndCreateEvent(ctx, prefixClaim, netboxv1.ConditionPrefixAssignedFalse, corev1.EventTypeWarning, err.Error()); setConditionErr != nil { + return ctrl.Result{}, fmt.Errorf("error updating status: %w, when look up of prefix by hash failed: %w", setConditionErr, err) + } + return ctrl.Result{Requeue: true}, nil } - // TODO: set condition for each error if prefixModel == nil { // Prefix cannot be restored from netbox @@ -135,11 +139,9 @@ func (r *PrefixClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) }, }) if err != nil { - setConditionErr := r.SetConditionAndCreateEvent(ctx, prefixClaim, netboxv1.ConditionPrefixAssignedFalse, corev1.EventTypeWarning, err.Error()) - if setConditionErr != nil { - return ctrl.Result{}, fmt.Errorf("error updating status: %w, when getting an available Prefix failed: %w", setConditionErr, err) + if setConditionErr := r.SetConditionAndCreateEvent(ctx, prefixClaim, netboxv1.ConditionPrefixAssignedFalse, corev1.EventTypeWarning, err.Error()); setConditionErr != nil { + return ctrl.Result{}, fmt.Errorf("error updating status: %w, when failed to get matching prefix: %w", setConditionErr, err) } - return ctrl.Result{Requeue: true}, nil } debugLogger.Info(fmt.Sprintf("prefix is not reserved in netbox, assignined new prefix: %s", prefixModel.Prefix)) @@ -220,38 +222,6 @@ func (r *PrefixClaimReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *PrefixClaimReconciler) GetAvailablePrefix(o *netboxv1.PrefixClaim) (*models.Prefix, error) { - var availablePrefix *models.Prefix - var err error - if availablePrefix, err = r.NetboxClient.GetAvailablePrefixByClaim( - &models.PrefixClaim{ - ParentPrefix: o.Spec.ParentPrefix, - PrefixLength: o.Spec.PrefixLength, - Metadata: &models.NetboxMetadata{ - Tenant: o.Spec.Tenant, - }, - }, - ); err != nil { - return nil, err - } - - if _, err = r.NetboxClient.ReserveOrUpdatePrefix( - &models.Prefix{ - Prefix: availablePrefix.Prefix, - Metadata: &models.NetboxMetadata{ - Comments: o.Spec.Comments, - Custom: map[string]string{}, - Description: o.Spec.Description, - Site: o.Spec.Site, - Tenant: o.Spec.Tenant, - }, - }); err != nil { - return nil, err - } - - return availablePrefix, nil -} - // TODO(henrybear327): Duplicated code, consider refactoring this func (r *PrefixClaimReconciler) SetConditionAndCreateEvent(ctx context.Context, o *netboxv1.PrefixClaim, condition metav1.Condition, eventType string, conditionMessageAppend string) error { if len(conditionMessageAppend) > 0 { diff --git a/internal/controller/utils.go b/internal/controller/utils.go new file mode 100644 index 00000000..b335b130 --- /dev/null +++ b/internal/controller/utils.go @@ -0,0 +1,25 @@ +/* +Copyright 2024 Swisscom (Schweiz) AG. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "strings" +) + +func convertCIDRToLeaseLockName(cidr string) string { + return strings.ReplaceAll(strings.ReplaceAll(cidr, "/", "-"), ":", "-") +} diff --git a/pkg/netbox/api/ip_address_claim.go b/pkg/netbox/api/ip_address_claim.go index f1ea353b..7da1c59f 100644 --- a/pkg/netbox/api/ip_address_claim.go +++ b/pkg/netbox/api/ip_address_claim.go @@ -26,8 +26,17 @@ import ( "github.com/netbox-community/netbox-operator/pkg/netbox/utils" ) +type IPFamily int64 + +const ( + IPv4Family IPFamily = iota + 4 + _ // Skip 5 + IPv6Family +) + const ( - ipMask = "/32" + ipMaskIPv4 = "/32" + ipMaskIPv6 = "/128" ) func (r *NetboxClient) RestoreExistingIpByHash(customFieldName string, hash string) (*models.IPAddress, error) { @@ -80,6 +89,15 @@ func (r *NetboxClient) GetAvailableIpAddressByClaim(ipAddressClaim *models.IPAdd return nil, err } + var ipMask string + if responseAvailableIPs.Payload[0].Family == int64(IPv4Family) { + ipMask = ipMaskIPv4 + } else if responseAvailableIPs.Payload[0].Family == int64(IPv6Family) { + ipMask = ipMaskIPv6 + } else { + return nil, errors.New("available ip has unknown IP family") + } + ipAddress, err := r.SetIpAddressMask(responseAvailableIPs.Payload[0].Address, ipMask) if err != nil { return nil, err diff --git a/pkg/netbox/api/ip_address_claim_test.go b/pkg/netbox/api/ip_address_claim_test.go index 526a0748..7d1ddd8e 100644 --- a/pkg/netbox/api/ip_address_claim_test.go +++ b/pkg/netbox/api/ip_address_claim_test.go @@ -36,20 +36,33 @@ func TestIPAddressClaim(t *testing.T) { mockIPAddress := mock_interfaces.NewMockIpamInterface(ctrl) mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) - parentPrefixId := int64(3) - parentPrefix := "10.114.0.0" - ipAddress1 := "10.112.140.1/24" - ipAddress2 := "10.112.140.2/24" - singleIpAddress2 := "10.112.140.2/32" - - // example of available IP address - availAddress := func() []*netboxModels.AvailableIP { + // test data for IPv4 ip address claim + parentPrefixIdV4 := int64(3) + parentPrefixV4 := "10.114.0.0" + ipAddressV4_1 := "10.112.140.1/24" + ipAddressV4_2 := "10.112.140.2/24" + singleIpAddressV4_2 := "10.112.140.2/32" + + // example of available IPv4 IP addresses + availAddressesIPv4 := func() []*netboxModels.AvailableIP { return []*netboxModels.AvailableIP{ - {Address: ipAddress1}, - {Address: ipAddress2}, + { + Address: ipAddressV4_1, + Family: int64(IPv4Family), + }, + { + Address: ipAddressV4_2, + Family: int64(IPv4Family), + }, } } + // test data for IPv6 ip address claim + parentPrefixIdV6 := int64(4) + parentPrefixV6 := "2001:db8:85a3:8d3::/64" + ipAddressV6 := "2001:db8:85a3:8d3::2/64" + singleIpAddressV6 := "2001:db8:85a3:8d3::2/128" + // example of tenant tenantId := int64(2) tenantName := "Tenant1" @@ -69,10 +82,10 @@ func TestIPAddressClaim(t *testing.T) { t.Run("Fetch available IP address's claim by parent prefix.", func(t *testing.T) { // ip address mock input - input := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixId) + input := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixIdV4) // ip address mock output output := &ipam.IpamPrefixesAvailableIpsListOK{ - Payload: availAddress(), + Payload: availAddressesIPv4(), } mockIPAddress.EXPECT().IpamPrefixesAvailableIpsList(input, nil).Return(output, nil) @@ -82,37 +95,40 @@ func TestIPAddressClaim(t *testing.T) { Ipam: mockIPAddress, } - actual, err := client.GetAvailableIpAddressesByParentPrefix(parentPrefixId) + actual, err := client.GetAvailableIpAddressesByParentPrefix(parentPrefixIdV4) // assert error return AssertNil(t, err) assert.Len(t, actual.Payload, 2) - assert.Equal(t, ipAddress1, actual.Payload[0].Address) - assert.Equal(t, ipAddress2, actual.Payload[1].Address) + assert.Equal(t, ipAddressV4_1, actual.Payload[0].Address) + assert.Equal(t, ipAddressV4_2, actual.Payload[1].Address) }) - t.Run("Fetch first available IP address by claim.", func(t *testing.T) { + t.Run("Fetch first available IP address by claim (IPv4).", func(t *testing.T) { inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) // ip address mock input - input := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefix) + input := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefixV4) // ip address mock output output := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{ { - ID: parentPrefixId, - Prefix: &parentPrefix, + ID: parentPrefixIdV4, + Prefix: &parentPrefixV4, }, }, }, } - inputIps := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixId) + inputIps := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixIdV4) outputIps := &ipam.IpamPrefixesAvailableIpsListOK{ Payload: []*netboxModels.AvailableIP{ - {Address: ipAddress2}, + { + Address: ipAddressV4_2, + Family: int64(IPv4Family), + }, }} mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() @@ -126,7 +142,7 @@ func TestIPAddressClaim(t *testing.T) { } actual, err := client.GetAvailableIpAddressByClaim(&models.IPAddressClaim{ - ParentPrefix: parentPrefix, + ParentPrefix: parentPrefixV4, Metadata: &models.NetboxMetadata{ Tenant: tenantName, }, @@ -135,7 +151,109 @@ func TestIPAddressClaim(t *testing.T) { // assert error AssertNil(t, err) // assert nil output - assert.Equal(t, singleIpAddress2, actual.IpAddress) + assert.Equal(t, singleIpAddressV4_2, actual.IpAddress) + }) + + t.Run("Fetch first available IP address by claim (IPv6).", func(t *testing.T) { + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + + // ip address mock input + input := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefixV6) + // ip address mock output + output := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixIdV6, + Prefix: &parentPrefixV6, + }, + }, + }, + } + + inputIps := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixIdV6) + outputIps := &ipam.IpamPrefixesAvailableIpsListOK{ + Payload: []*netboxModels.AvailableIP{ + { + Address: ipAddressV6, + Family: int64(IPv6Family), + }, + }} + + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + mockIPAddress.EXPECT().IpamPrefixesList(input, nil).Return(output, nil) + mockIPAddress.EXPECT().IpamPrefixesAvailableIpsList(inputIps, nil).Return(outputIps, nil) + + // init client + client := &NetboxClient{ + Tenancy: mockTenancy, + Ipam: mockIPAddress, + } + + actual, err := client.GetAvailableIpAddressByClaim(&models.IPAddressClaim{ + ParentPrefix: parentPrefixV6, + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + }, + }) + + // assert error + AssertNil(t, err) + // assert nil output + assert.Equal(t, singleIpAddressV6, actual.IpAddress) + }) + + t.Run("Fetch first available IP address by claim (invalid IP family).", func(t *testing.T) { + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + + // ip address mock input + input := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefixV6) + // ip address mock output + output := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixIdV6, + Prefix: &parentPrefixV6, + }, + }, + }, + } + + inputIps := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixIdV6) + outputIps := &ipam.IpamPrefixesAvailableIpsListOK{ + Payload: []*netboxModels.AvailableIP{ + { + Address: ipAddressV6, + Family: int64(5), + }, + }} + + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + mockIPAddress.EXPECT().IpamPrefixesList(input, nil).Return(output, nil) + mockIPAddress.EXPECT().IpamPrefixesAvailableIpsList(inputIps, nil).Return(outputIps, nil) + + // init client + client := &NetboxClient{ + Tenancy: mockTenancy, + Ipam: mockIPAddress, + } + + actual, err := client.GetAvailableIpAddressByClaim(&models.IPAddressClaim{ + ParentPrefix: parentPrefixV6, + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + }, + }) + + // assert error + AssertError(t, err, "available ip has unknown IP family") + + var expected *models.IPAddress + + assert.Equal(t, expected, actual) }) t.Run("Fetch IP address's claim with incorrect parent prefix.", func(t *testing.T) { @@ -143,7 +261,7 @@ func TestIPAddressClaim(t *testing.T) { inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) // ip address mock input - input := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefix) + input := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefixV4) // ip address mock output output := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ @@ -161,7 +279,7 @@ func TestIPAddressClaim(t *testing.T) { } actual, err := client.GetAvailableIpAddressByClaim(&models.IPAddressClaim{ - ParentPrefix: parentPrefix, + ParentPrefix: parentPrefixV4, Metadata: &models.NetboxMetadata{ Tenant: tenantName, }, @@ -178,7 +296,7 @@ func TestIPAddressClaim(t *testing.T) { t.Run("Fetch IP address's claim with exhausted parent prefix.", func(t *testing.T) { // ip address mock input - input := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixId) + input := ipam.NewIpamPrefixesAvailableIpsListParams().WithID(parentPrefixIdV4) // ip address mock output output := &ipam.IpamPrefixesAvailableIpsListOK{ Payload: []*netboxModels.AvailableIP{}, @@ -191,7 +309,7 @@ func TestIPAddressClaim(t *testing.T) { Ipam: mockIPAddress, } - actual, err := client.GetAvailableIpAddressesByParentPrefix(parentPrefixId) + actual, err := client.GetAvailableIpAddressesByParentPrefix(parentPrefixIdV4) // assert error AssertError(t, err, "parent prefix exhausted") diff --git a/pkg/netbox/api/prefix_claim.go b/pkg/netbox/api/prefix_claim.go index e5191ecf..a6de143a 100644 --- a/pkg/netbox/api/prefix_claim.go +++ b/pkg/netbox/api/prefix_claim.go @@ -53,16 +53,31 @@ func (r *NetboxClient) RestoreExistingPrefixByHash(hash string) (*models.Prefix, }, nil } -func isRequestingTheEntireParentPrefix(prefixClaim *models.PrefixClaim) (bool, error) { +func validatePrefixLengthOrError(prefixClaim *models.PrefixClaim, prefixFamily int64) error { parentPrefixSplit := strings.Split(prefixClaim.ParentPrefix, "/") if len(parentPrefixSplit) != 2 { - return false, errors.New("invalid parent prefix format") + return errors.New("invalid parent prefix format") } - if "/"+parentPrefixSplit[1] /* e.g. /24 */ == prefixClaim.PrefixLength /* e.g. /24 */ { - return true, nil + parentPrefixLength, err := strconv.Atoi(parentPrefixSplit[1]) + if err != nil { + return err + } + + requestedPrefixLength, err := strconv.Atoi(strings.TrimPrefix(prefixClaim.PrefixLength, "/")) + if err != nil { + return err + } + + if parentPrefixLength == requestedPrefixLength { + return errors.New("requesting the entire parent prefix range is disallowed") + } else if parentPrefixLength > requestedPrefixLength { + return errors.New("requested prefix size must be smaller than the parent prefix size") + } else if prefixFamily == int64(IPv4Family) && requestedPrefixLength > 32 { + return errors.New("requested prefix length must be smaller than 32 for IPv4") } - return false, nil + + return nil } // GetAvailablePrefixByClaim searches an available Prefix in Netbox matching PrefixClaim requirements @@ -83,17 +98,12 @@ func (r *NetboxClient) GetAvailablePrefixByClaim(prefixClaim *models.PrefixClaim return nil, errors.New("parent prefix not found") } - parentPrefixId := responseParentPrefix.Payload.Results[0].ID - - // We reject target prefix size == parent prefix size - if ret, err := isRequestingTheEntireParentPrefix(prefixClaim); err != nil { + if err := validatePrefixLengthOrError(prefixClaim, *responseParentPrefix.Payload.Results[0].Family.Value); err != nil { return nil, err - } else if ret { - // The issue lies in the prefix deletion. - // We do not want the operator to delete the parent prefix as a side effect of deleting any allocated prefixes - return nil, errors.New("requesting for the entire parent prefix range is disallowed") } + parentPrefixId := responseParentPrefix.Payload.Results[0].ID + /* Notes regarding the available prefix returned by netbox The available prefixes API do NOT allow us to pass in the desired prefix size. And we observed the API's behavior as the following: diff --git a/pkg/netbox/api/prefix_claim_test.go b/pkg/netbox/api/prefix_claim_test.go index 831f4367..f05c20ac 100644 --- a/pkg/netbox/api/prefix_claim_test.go +++ b/pkg/netbox/api/prefix_claim_test.go @@ -44,8 +44,14 @@ func TestPrefixClaim_GetAvailablePrefixesByParentPrefix(t *testing.T) { childPrefix2 := "10.120.180.0/24" availablePrefixListOutput := &ipam.IpamPrefixesAvailablePrefixesListOK{ Payload: []*netboxModels.AvailablePrefix{ - {Prefix: childPrefix1}, - {Prefix: childPrefix2}, + { + Prefix: childPrefix1, + Family: int64(IPv4Family), + }, + { + Prefix: childPrefix2, + Family: int64(IPv4Family), + }, }, } @@ -111,6 +117,7 @@ func TestPrefixClaim_GetAvailablePrefixByClaim_WithWrongParent(t *testing.T) { prefixListInput := ipam. NewIpamPrefixesListParams(). WithPrefix(&prefix) + prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{}, @@ -166,12 +173,16 @@ func TestPrefixClaim_GetBestFitPrefixByClaim(t *testing.T) { prefixListInput := ipam. NewIpamPrefixesListParams(). WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{ { ID: parentPrefixId, Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, }, }, }, @@ -209,6 +220,279 @@ func TestPrefixClaim_GetBestFitPrefixByClaim(t *testing.T) { assert.Equal(t, prefix, actual.Prefix) } +func TestPrefixClaim_InvalidIPv4PrefixLength(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockPrefixIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // example of tenant + tenantId := int64(2) + tenantName := "Tenant1" + tenantOutputSlug := "tenant1" + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + parentPrefix := "10.112.140.0/24" + parentPrefixId := int64(1) + prefixListInput := ipam. + NewIpamPrefixesListParams(). + WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 + prefixListOutput := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixId, + Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, + }, + }, + }, + } + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + + mockPrefixIpam.EXPECT().IpamPrefixesList(prefixListInput, nil).Return(prefixListOutput, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + netboxClient := &NetboxClient{ + Ipam: mockPrefixIpam, + Tenancy: mockTenancy, + } + + actual, err := netboxClient.GetAvailablePrefixByClaim(&models.PrefixClaim{ + ParentPrefix: parentPrefix, + PrefixLength: "/33", + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + }, + }) + + var expectedPrefix *models.Prefix + + assert.Error(t, err) + assert.Equal(t, expectedPrefix, actual) +} + +func TestPrefixClaim_FailWhenRequestingEntirePrefix(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockPrefixIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // example of tenant + tenantId := int64(2) + tenantName := "Tenant1" + tenantOutputSlug := "tenant1" + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + parentPrefix := "10.112.140.0/24" + parentPrefixId := int64(1) + prefixListInput := ipam. + NewIpamPrefixesListParams(). + WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 + prefixListOutput := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixId, + Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, + }, + }, + }, + } + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + + mockPrefixIpam.EXPECT().IpamPrefixesList(prefixListInput, nil).Return(prefixListOutput, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + netboxClient := &NetboxClient{ + Ipam: mockPrefixIpam, + Tenancy: mockTenancy, + } + + actual, err := netboxClient.GetAvailablePrefixByClaim(&models.PrefixClaim{ + ParentPrefix: parentPrefix, + PrefixLength: "/24", + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + }, + }) + + var expectedPrefix *models.Prefix + + assert.Error(t, err) + assert.Equal(t, expectedPrefix, actual) +} + +func TestPrefixClaim_FailWhenPrefixLargerThanParent(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockPrefixIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // example of tenant + tenantId := int64(2) + tenantName := "Tenant1" + tenantOutputSlug := "tenant1" + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + parentPrefix := "10.112.140.0/24" + parentPrefixId := int64(1) + prefixListInput := ipam. + NewIpamPrefixesListParams(). + WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 + prefixListOutput := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixId, + Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, + }, + }, + }, + } + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + + mockPrefixIpam.EXPECT().IpamPrefixesList(prefixListInput, nil).Return(prefixListOutput, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + netboxClient := &NetboxClient{ + Ipam: mockPrefixIpam, + Tenancy: mockTenancy, + } + + actual, err := netboxClient.GetAvailablePrefixByClaim(&models.PrefixClaim{ + ParentPrefix: parentPrefix, + PrefixLength: "/20", + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + }, + }) + + var expectedPrefix *models.Prefix + + assert.Error(t, err) + assert.Equal(t, expectedPrefix, actual) +} + +func TestPrefixClaim_ValidIPv6PrefixLength(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockPrefixIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // example of tenant + tenantId := int64(2) + tenantName := "Tenant1" + tenantOutputSlug := "tenant1" + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + parentPrefix := "2001:db8:85a3:8d3::/30" + parentPrefixId := int64(1) + prefix := "2001:db8:85a3:8d3::/33" + prefixListInput := ipam. + NewIpamPrefixesListParams(). + WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv6Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV6 + prefixListOutput := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixId, + Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, + }, + }, + }, + } + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + + prefixAvailableListInput := ipam.NewIpamPrefixesAvailablePrefixesListParams().WithID(parentPrefixId) + prefixAvailableListOutput := &ipam.IpamPrefixesAvailablePrefixesListOK{ + Payload: []*netboxModels.AvailablePrefix{ + { + Prefix: prefix, + }, + }, + } + + mockPrefixIpam.EXPECT().IpamPrefixesList(prefixListInput, nil).Return(prefixListOutput, nil).AnyTimes() + mockPrefixIpam.EXPECT().IpamPrefixesAvailablePrefixesList(prefixAvailableListInput, nil).Return(prefixAvailableListOutput, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + netboxClient := &NetboxClient{ + Ipam: mockPrefixIpam, + Tenancy: mockTenancy, + } + + actual, err := netboxClient.GetAvailablePrefixByClaim(&models.PrefixClaim{ + ParentPrefix: parentPrefix, + PrefixLength: "/33", + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + }, + }) + + assert.Nil(t, err) + assert.Equal(t, prefix, actual.Prefix) +} + func TestPrefixClaim_GetBestFitPrefixByClaimNoAvailablePrefixMatchesSize(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -239,12 +523,16 @@ func TestPrefixClaim_GetBestFitPrefixByClaimNoAvailablePrefixMatchesSize(t *test prefixListInput := ipam. NewIpamPrefixesListParams(). WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{ { ID: parentPrefixId, Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, }, }, }, @@ -316,12 +604,16 @@ func TestPrefixClaim_GetBestFitPrefixByClaimNoAvailablePrefixMatchesSizeCriteria prefixListInput := ipam. NewIpamPrefixesListParams(). WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{ { ID: parentPrefixId, Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, }, }, }, @@ -388,12 +680,16 @@ func TestPrefixClaim_GetBestFitPrefixByClaimInvalidFormatFromNetbox(t *testing.T prefixListInput := ipam. NewIpamPrefixesListParams(). WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{ { ID: parentPrefixId, Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, }, }, }, @@ -465,12 +761,16 @@ func TestPrefixClaim_GetBestFitPrefixByClaimInvalidPrefixClaim(t *testing.T) { prefixListInput := ipam. NewIpamPrefixesListParams(). WithPrefix(&parentPrefix) + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ Results: []*netboxModels.Prefix{ { ID: parentPrefixId, Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, }, }, },