From 549c3c61ec9f4833017dfffb1470714bfcf0fb23 Mon Sep 17 00:00:00 2001 From: Christian Ang Date: Wed, 19 Apr 2023 22:44:33 +0000 Subject: [PATCH] Add out of range ip count to pool status Co-authored-by: Aidan Obley --- api/v1alpha1/inclusterippool_types.go | 5 ++ ...uster.x-k8s.io_globalinclusterippools.yaml | 6 ++ ...pam.cluster.x-k8s.io_inclusterippools.yaml | 6 ++ internal/controllers/inclusterippool.go | 11 +++- internal/controllers/inclusterippool_test.go | 61 +++++++++++++++++++ internal/poolutil/pool.go | 16 +++++ internal/webhooks/inclusterippool.go | 2 +- 7 files changed, 103 insertions(+), 4 deletions(-) diff --git a/api/v1alpha1/inclusterippool_types.go b/api/v1alpha1/inclusterippool_types.go index c1b84db..e7641b9 100644 --- a/api/v1alpha1/inclusterippool_types.go +++ b/api/v1alpha1/inclusterippool_types.go @@ -59,6 +59,11 @@ type InClusterIPPoolStatusIPAddresses struct { // Used is the count of allocated IPs in the pool. // Counts greater than int can contain will report as math.MaxInt. Used int `json:"used"` + + // Out of Range is the count of allocated IPs in the pool that is not + // contained within spec.Addresses. + // Counts greater than int can contain will report as math.MaxInt. + OutOfRange int `json:"outOfRange"` } // +kubebuilder:object:root=true diff --git a/config/crd/bases/ipam.cluster.x-k8s.io_globalinclusterippools.yaml b/config/crd/bases/ipam.cluster.x-k8s.io_globalinclusterippools.yaml index c8ae691..51170a6 100644 --- a/config/crd/bases/ipam.cluster.x-k8s.io_globalinclusterippools.yaml +++ b/config/crd/bases/ipam.cluster.x-k8s.io_globalinclusterippools.yaml @@ -103,6 +103,11 @@ spec: description: Free is the count of unallocated IPs in the pool. Counts greater than int can contain will report as math.MaxInt. type: integer + outOfRange: + description: Out of Range is the count of allocated IPs in the + pool that is not contained within spec.Addresses. Counts greater + than int can contain will report as math.MaxInt. + type: integer total: description: Total is the total number of IPs configured for the pool. Counts greater than int can contain will report as math.MaxInt. @@ -113,6 +118,7 @@ spec: type: integer required: - free + - outOfRange - total - used type: object diff --git a/config/crd/bases/ipam.cluster.x-k8s.io_inclusterippools.yaml b/config/crd/bases/ipam.cluster.x-k8s.io_inclusterippools.yaml index d40c41d..0ac8963 100644 --- a/config/crd/bases/ipam.cluster.x-k8s.io_inclusterippools.yaml +++ b/config/crd/bases/ipam.cluster.x-k8s.io_inclusterippools.yaml @@ -105,6 +105,11 @@ spec: description: Free is the count of unallocated IPs in the pool. Counts greater than int can contain will report as math.MaxInt. type: integer + outOfRange: + description: Out of Range is the count of allocated IPs in the + pool that is not contained within spec.Addresses. Counts greater + than int can contain will report as math.MaxInt. + type: integer total: description: Total is the total number of IPs configured for the pool. Counts greater than int can contain will report as math.MaxInt. @@ -115,6 +120,7 @@ spec: type: integer required: - free + - outOfRange - total - used type: object diff --git a/internal/controllers/inclusterippool.go b/internal/controllers/inclusterippool.go index f862063..2c13bab 100644 --- a/internal/controllers/inclusterippool.go +++ b/internal/controllers/inclusterippool.go @@ -199,11 +199,16 @@ func genericReconcile(ctx context.Context, c client.Client, pool pooltypes.Gener } free := poolCount - inUseCount + outOfRangeIPSet, err := poolutil.AddressesOutOfRangeIPSet(addressesInUse, poolIPSet) + if err != nil { + return ctrl.Result{}, errors.Wrap(err, "failed to build out of range ip set") + } pool.PoolStatus().Addresses = &v1alpha1.InClusterIPPoolStatusIPAddresses{ - Total: poolCount, - Used: inUseCount, - Free: free, + Total: poolCount, + Used: inUseCount, + Free: free, + OutOfRange: poolutil.IPSetCount(outOfRangeIPSet), } log.Info("Updating pool with usage info", "statusAddresses", pool.PoolStatus().Addresses) diff --git a/internal/controllers/inclusterippool_test.go b/internal/controllers/inclusterippool_test.go index 6413faa..124f41a 100644 --- a/internal/controllers/inclusterippool_test.go +++ b/internal/controllers/inclusterippool_test.go @@ -99,6 +99,67 @@ var _ = Describe("IP Pool Reconciler", func() { Entry("When there is 1 claim with gateway outside of range - GlobalInClusterIPPool", "GlobalInClusterIPPool", []string{"10.0.0.10-10.0.0.20"}, "10.0.0.1", 11, 1, 10), ) + + DescribeTable("it shows the out of range ips if any", + func(poolType string, addresses []string, gateway string, updatedAddresses []string, numClaims, expectedOutOfRange int) { + poolSpec := v1alpha1.InClusterIPPoolSpec{ + Prefix: 24, + Gateway: gateway, + Addresses: addresses, + } + + switch poolType { + case "InClusterIPPool": + genericPool = &v1alpha1.InClusterIPPool{ + ObjectMeta: metav1.ObjectMeta{GenerateName: testPool, Namespace: namespace}, + Spec: poolSpec, + } + case "GlobalInClusterIPPool": + genericPool = &v1alpha1.GlobalInClusterIPPool{ + ObjectMeta: metav1.ObjectMeta{GenerateName: testPool, Namespace: namespace}, + Spec: poolSpec, + } + default: + Fail("Unknown pool type") + } + + Expect(k8sClient.Create(context.Background(), genericPool)).To(Succeed()) + + for i := 0; i < numClaims; i++ { + claim := ipamv1.IPAddressClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("test%d", i), + Namespace: namespace, + }, + Spec: ipamv1.IPAddressClaimSpec{ + PoolRef: corev1.TypedLocalObjectReference{ + APIGroup: pointer.String("ipam.cluster.x-k8s.io"), + Kind: poolType, + Name: genericPool.GetName(), + }, + }, + } + Expect(k8sClient.Create(context.Background(), &claim)).To(Succeed()) + createdClaimNames = append(createdClaimNames, claim.Name) + } + + Eventually(Object(genericPool)). + WithTimeout(5 * time.Second).WithPolling(100 * time.Millisecond).Should( + HaveField("Status.Addresses.Used", Equal(numClaims))) + + genericPool.PoolSpec().Addresses = updatedAddresses + Expect(k8sClient.Update(context.Background(), genericPool)).To(Succeed()) + + Eventually(Object(genericPool)). + WithTimeout(5 * time.Second).WithPolling(100 * time.Millisecond).Should( + HaveField("Status.Addresses.OutOfRange", Equal(expectedOutOfRange))) + }, + + Entry("InClusterIPPool", + "InClusterIPPool", []string{"10.0.0.10-10.0.0.20"}, "10.0.0.1", []string{"10.0.0.13-10.0.0.20"}, 5, 3), + Entry("GlobalInClusterIPPool", + "GlobalInClusterIPPool", []string{"10.0.0.10-10.0.0.20"}, "10.0.0.1", []string{"10.0.0.13-10.0.0.20"}, 5, 3), + ) }) Context("when the pool has IPAddresses", func() { diff --git a/internal/poolutil/pool.go b/internal/poolutil/pool.go index db5483a..7196cd2 100644 --- a/internal/poolutil/pool.go +++ b/internal/poolutil/pool.go @@ -19,6 +19,22 @@ import ( "github.com/telekom/cluster-api-ipam-provider-in-cluster/internal/index" ) +// AddressesOutOfRangeIPSet returns an IPSet of the inUseAddresses IPs that are +// not in the poolIPSet. +func AddressesOutOfRangeIPSet(inUseAddresses []ipamv1.IPAddress, poolIPSet *netipx.IPSet) (*netipx.IPSet, error) { + outOfRangeBuilder := &netipx.IPSetBuilder{} + for _, address := range inUseAddresses { + ip, err := netip.ParseAddr(address.Spec.Address) + if err != nil { + // if an address we fetch for the pool is unparsable then it isn't in the pool ranges + continue + } + outOfRangeBuilder.Add(ip) + } + outOfRangeBuilder.RemoveSet(poolIPSet) + return outOfRangeBuilder.IPSet() +} + // ListAddressesInUse fetches all IPAddresses belonging to the specified pool. // Note: requires `index.ipAddressByCombinedPoolRef` to be set up. func ListAddressesInUse(ctx context.Context, c client.Reader, namespace string, poolRef corev1.TypedLocalObjectReference) ([]ipamv1.IPAddress, error) { diff --git a/internal/webhooks/inclusterippool.go b/internal/webhooks/inclusterippool.go index b4ad739..9ea3830 100644 --- a/internal/webhooks/inclusterippool.go +++ b/internal/webhooks/inclusterippool.go @@ -154,7 +154,7 @@ func (webhook *InClusterIPPool) ValidateUpdate(ctx context.Context, oldObj, newO inUseBuilder.RemoveSet(newPoolIPSet) outOfRangeIPSet, err := inUseBuilder.IPSet() if err != nil { - panic("oh no") + return apierrors.NewInternalError(err) } if outOfRange := outOfRangeIPSet.Ranges(); len(outOfRange) > 0 {