Skip to content

Commit 891c3c7

Browse files
christianangAidan Obley
authored andcommitted
Add out of range ip count to pool status
Co-authored-by: Aidan Obley <[email protected]>
1 parent 677bd64 commit 891c3c7

9 files changed

+113
-10
lines changed

api/v1alpha1/inclusterippool_types.go

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ type InClusterIPPoolStatusIPAddresses struct {
5959
// Used is the count of allocated IPs in the pool.
6060
// Counts greater than int can contain will report as math.MaxInt.
6161
Used int `json:"used"`
62+
63+
// Out of Range is the count of allocated IPs in the pool that is not
64+
// contained within spec.Addresses.
65+
// Counts greater than int can contain will report as math.MaxInt.
66+
OutOfRange int `json:"outOfRange"`
6267
}
6368

6469
// +kubebuilder:object:root=true

config/crd/bases/ipam.cluster.x-k8s.io_globalinclusterippools.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ spec:
103103
description: Free is the count of unallocated IPs in the pool.
104104
Counts greater than int can contain will report as math.MaxInt.
105105
type: integer
106+
outOfRange:
107+
description: Out of Range is the count of allocated IPs in the
108+
pool that is not contained within spec.Addresses. Counts greater
109+
than int can contain will report as math.MaxInt.
110+
type: integer
106111
total:
107112
description: Total is the total number of IPs configured for the
108113
pool. Counts greater than int can contain will report as math.MaxInt.
@@ -113,6 +118,7 @@ spec:
113118
type: integer
114119
required:
115120
- free
121+
- outOfRange
116122
- total
117123
- used
118124
type: object

config/crd/bases/ipam.cluster.x-k8s.io_inclusterippools.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ spec:
105105
description: Free is the count of unallocated IPs in the pool.
106106
Counts greater than int can contain will report as math.MaxInt.
107107
type: integer
108+
outOfRange:
109+
description: Out of Range is the count of allocated IPs in the
110+
pool that is not contained within spec.Addresses. Counts greater
111+
than int can contain will report as math.MaxInt.
112+
type: integer
108113
total:
109114
description: Total is the total number of IPs configured for the
110115
pool. Counts greater than int can contain will report as math.MaxInt.
@@ -115,6 +120,7 @@ spec:
115120
type: integer
116121
required:
117122
- free
123+
- outOfRange
118124
- total
119125
- used
120126
type: object

internal/controllers/inclusterippool.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,16 @@ func genericReconcile(ctx context.Context, c client.Client, pool pooltypes.Gener
183183

184184
inUseCount := len(addressesInUse)
185185
free := poolCount - inUseCount
186+
outOfRangeIPSet, err := poolutil.AddressesOutOfRangeIPSet(addressesInUse, poolIPSet)
187+
if err != nil {
188+
return ctrl.Result{}, errors.Wrap(err, "failed to build out of range ip set")
189+
}
186190

187191
pool.PoolStatus().Addresses = &v1alpha1.InClusterIPPoolStatusIPAddresses{
188-
Total: poolCount,
189-
Used: inUseCount,
190-
Free: free,
192+
Total: poolCount,
193+
Used: inUseCount,
194+
Free: free,
195+
OutOfRange: poolutil.IPSetCount(outOfRangeIPSet),
191196
}
192197

193198
log.Info("Updating pool with usage info", "statusAddresses", pool.PoolStatus().Addresses)

internal/controllers/inclusterippool_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,66 @@ var _ = Describe("IP Pool Reconciler", func() {
119119
Entry("When there is 1 claim with gateway outside of range - GlobalInClusterIPPool",
120120
"GlobalInClusterIPPool", []string{"10.0.0.10-10.0.0.20"}, "10.0.0.1", 11, 1, 10),
121121
)
122+
123+
DescribeTable("it shows the out of range ips if any",
124+
func(poolType string, addresses []string, gateway string, updatedAddresses []string, numClaims, expectedOutOfRange int) {
125+
poolSpec := v1alpha1.InClusterIPPoolSpec{
126+
Prefix: 24,
127+
Gateway: gateway,
128+
Addresses: addresses,
129+
}
130+
131+
switch poolType {
132+
case "InClusterIPPool":
133+
genericPool = &v1alpha1.InClusterIPPool{
134+
ObjectMeta: metav1.ObjectMeta{GenerateName: testPool, Namespace: namespace},
135+
Spec: poolSpec,
136+
}
137+
case "GlobalInClusterIPPool":
138+
genericPool = &v1alpha1.GlobalInClusterIPPool{
139+
ObjectMeta: metav1.ObjectMeta{GenerateName: testPool, Namespace: namespace},
140+
Spec: poolSpec,
141+
}
142+
default:
143+
Fail("Unknown pool type")
144+
}
145+
146+
Expect(k8sClient.Create(context.Background(), genericPool)).To(Succeed())
147+
148+
for i := 0; i < numClaims; i++ {
149+
claim := clusterv1.IPAddressClaim{
150+
ObjectMeta: metav1.ObjectMeta{
151+
Name: fmt.Sprintf("test%d", i),
152+
Namespace: namespace,
153+
},
154+
Spec: clusterv1.IPAddressClaimSpec{
155+
PoolRef: corev1.TypedLocalObjectReference{
156+
APIGroup: pointer.String("ipam.cluster.x-k8s.io"),
157+
Kind: poolType,
158+
Name: genericPool.GetName(),
159+
},
160+
},
161+
}
162+
Expect(k8sClient.Create(context.Background(), &claim)).To(Succeed())
163+
createdClaimNames = append(createdClaimNames, claim.Name)
164+
}
165+
166+
Eventually(Object(genericPool)).
167+
WithTimeout(5 * time.Second).WithPolling(100 * time.Millisecond).Should(
168+
HaveField("Status.Addresses.Used", Equal(numClaims)))
169+
170+
genericPool.PoolSpec().Addresses = updatedAddresses
171+
Expect(k8sClient.Update(context.Background(), genericPool)).To(Succeed())
172+
173+
Eventually(Object(genericPool)).
174+
WithTimeout(5 * time.Second).WithPolling(100 * time.Millisecond).Should(
175+
HaveField("Status.Addresses.OutOfRange", Equal(expectedOutOfRange)))
176+
},
177+
178+
Entry("InClusterIPPool",
179+
"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),
180+
Entry("GlobalInClusterIPPool",
181+
"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),
182+
)
122183
})
123184
})

internal/index/index.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const (
2323
func SetupIndexes(ctx context.Context, mgr manager.Manager) error {
2424
err := mgr.GetCache().IndexField(ctx, &ipamv1.IPAddress{},
2525
IPAddressPoolRefCombinedField,
26-
ipAddressByCombinedPoolRef,
26+
IPAddressByCombinedPoolRef,
2727
)
2828
if err != nil {
2929
return err
@@ -35,7 +35,8 @@ func SetupIndexes(ctx context.Context, mgr manager.Manager) error {
3535
)
3636
}
3737

38-
func ipAddressByCombinedPoolRef(o client.Object) []string {
38+
// IPAddressByCombinedPoolRef fulfills the IndexerFunc for IPAddress poolRefs
39+
func IPAddressByCombinedPoolRef(o client.Object) []string {
3940
ip, ok := o.(*ipamv1.IPAddress)
4041
if !ok {
4142
panic(fmt.Sprintf("Expected an IPAddress but got a %T", o))

internal/poolutil/pool.go

+16
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ import (
1919
"github.com/telekom/cluster-api-ipam-provider-in-cluster/internal/index"
2020
)
2121

22+
// AddressesOutOfRangeIPSet returns an IPSet of the inUseAddresses IPs that are
23+
// not in the poolIPSet.
24+
func AddressesOutOfRangeIPSet(inUseAddresses []ipamv1.IPAddress, poolIPSet *netipx.IPSet) (*netipx.IPSet, error) {
25+
outOfRangeBuilder := &netipx.IPSetBuilder{}
26+
for _, address := range inUseAddresses {
27+
ip, err := netip.ParseAddr(address.Spec.Address)
28+
if err != nil {
29+
// if an address we fetch for the pool is unparsable then it isn't in the pool ranges
30+
continue
31+
}
32+
outOfRangeBuilder.Add(ip)
33+
}
34+
outOfRangeBuilder.RemoveSet(poolIPSet)
35+
return outOfRangeBuilder.IPSet()
36+
}
37+
2238
// ListAddressesInUse fetches all IPAddresses belonging to the specified pool.
2339
// Note: requires `index.ipAddressByCombinedPoolRef` to be set up.
2440
func ListAddressesInUse(ctx context.Context, c client.Reader, namespace string, poolRef corev1.TypedLocalObjectReference) ([]ipamv1.IPAddress, error) {

internal/webhooks/inclusterippool.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import (
55
"fmt"
66
"net/netip"
77

8-
corev1 "k8s.io/api/core/v1"
9-
"k8s.io/utils/pointer"
10-
118
"go4.org/netipx"
9+
corev1 "k8s.io/api/core/v1"
1210
apierrors "k8s.io/apimachinery/pkg/api/errors"
1311
"k8s.io/apimachinery/pkg/runtime"
1412
"k8s.io/apimachinery/pkg/util/validation/field"
13+
"k8s.io/utils/pointer"
1514
ctrl "sigs.k8s.io/controller-runtime"
1615
"sigs.k8s.io/controller-runtime/pkg/client"
1716
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -155,7 +154,7 @@ func (webhook *InClusterIPPool) ValidateUpdate(ctx context.Context, oldObj, newO
155154
inUseBuilder.RemoveSet(newPoolIPSet)
156155
outOfRangeIPSet, err := inUseBuilder.IPSet()
157156
if err != nil {
158-
panic("oh no")
157+
return apierrors.NewInternalError(err)
159158
}
160159

161160
if outOfRange := outOfRangeIPSet.Ranges(); len(outOfRange) > 0 {

internal/webhooks/inclusterippool_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1515

1616
"github.com/telekom/cluster-api-ipam-provider-in-cluster/api/v1alpha1"
17+
"github.com/telekom/cluster-api-ipam-provider-in-cluster/internal/index"
1718
"github.com/telekom/cluster-api-ipam-provider-in-cluster/pkg/types"
1819
)
1920

@@ -224,7 +225,10 @@ func TestInClusterIPPoolDefaulting(t *testing.T) {
224225
scheme := runtime.NewScheme()
225226
g.Expect(ipamv1.AddToScheme(scheme)).To(Succeed())
226227
webhook := InClusterIPPool{
227-
Client: fake.NewClientBuilder().WithScheme(scheme).Build(),
228+
Client: fake.NewClientBuilder().
229+
WithScheme(scheme).
230+
WithIndex(&ipamv1.IPAddress{}, index.IPAddressPoolRefCombinedField, index.IPAddressByCombinedPoolRef).
231+
Build(),
228232
}
229233

230234
t.Run(tt.name, customDefaultValidateTest(ctx, namespacedPool.DeepCopyObject(), &webhook))

0 commit comments

Comments
 (0)