Skip to content

Commit c8a6082

Browse files
Merge pull request #997 from ksahil12/feature-allocate-IP
✨ Adding feature for allocating requested IP
2 parents 71920b2 + 565bc08 commit c8a6082

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed

examples/ippool/ippool.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ apiVersion: ipam.metal3.io/v1alpha1
3535
kind: IPClaim
3636
metadata:
3737
name: ${CLUSTER_NAME}-controlplane-template-0-provisioning-pool
38+
annotations:
39+
ipAddress: <optional-annotation-for-specific-ip-request>
3840
spec:
3941
pool:
4042
name: pool1
@@ -44,6 +46,8 @@ apiVersion: ipam.cluster.x-k8s.io/v1beta1
4446
kind: IPAddressClaim
4547
metadata:
4648
name: ${CLUSTER_NAME}-controlplane-template-1-provisioning-pool
49+
annotations:
50+
ipAddress: <optional-annotation-for-specific-ip-request>
4751
spec:
4852
poolRef:
4953
apiGroup: ipam.metal3.io

ipam/ippool_manager.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var (
4141
const (
4242
IPAddressClaimFinalizer = "ipam.metal3.io/ipaddressclaim"
4343
IPAddressFinalizer = "ipam.metal3.io/ipaddress"
44+
IPAddressAnnotation = "ipAddress"
4445
)
4546

4647
// IPPoolManagerInterface is an interface for a IPPoolManager.
@@ -415,6 +416,15 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
415416

416417
ipAllocated := false
417418

419+
requestedIP := ipamv1.IPAddressStr(addressClaim.ObjectMeta.Annotations[IPAddressAnnotation])
420+
isRequestedIPAllocated := false
421+
422+
// Conflict-case, claim is preAllocated but has requested different IP
423+
if requestedIP != "" && ipPreAllocated && requestedIP != preAllocatedAddress {
424+
addressClaim.Status.ErrorMessage = ptr.To("PreAllocation and requested ip address are conflicting")
425+
return "", 0, nil, []ipamv1.IPAddressStr{}, errors.New("PreAllocation and requested ip address are conflicting")
426+
}
427+
418428
for _, pool := range m.IPPool.Spec.Pools {
419429
if ipAllocated {
420430
break
@@ -426,6 +436,13 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
426436
break
427437
}
428438
index++
439+
// Check if requestedIP is present and matches the current address
440+
if requestedIP != "" && allocatedAddress != requestedIP {
441+
continue
442+
}
443+
if requestedIP != "" && allocatedAddress == requestedIP {
444+
isRequestedIPAllocated = true
445+
}
429446
// We have a pre-allocated ip, we just need to ensure that it matches the current address
430447
// if it does not, continue and try the next address
431448
if ipPreAllocated && allocatedAddress != preAllocatedAddress {
@@ -455,6 +472,11 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
455472
}
456473
}
457474
}
475+
// We did not get requestedIp as it did not match with any available IP
476+
if requestedIP != "" && isRequestedIPAllocated && !ipAllocated {
477+
addressClaim.Status.ErrorMessage = ptr.To("Requested IP not available")
478+
return "", 0, nil, []ipamv1.IPAddressStr{}, errors.New("Requested IP not available")
479+
}
458480
// We have a preallocated IP but we did not find it in the pools! It means it is
459481
// misconfigured
460482
if !ipAllocated && ipPreAllocated {
@@ -485,6 +507,24 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1.IPAddressCla
485507

486508
ipAllocated := false
487509

510+
requestedIP := ipamv1.IPAddressStr(addressClaim.ObjectMeta.Annotations[IPAddressAnnotation])
511+
isRequestedIPAllocated := false
512+
513+
// Conflict-case, claim is preAllocated but has requested different IP
514+
if requestedIP != "" && ipPreAllocated && requestedIP != preAllocatedAddress {
515+
conditions := clusterv1.Conditions{}
516+
conditions = append(conditions, clusterv1.Condition{
517+
Type: "ErrorMessage",
518+
Status: corev1.ConditionTrue,
519+
LastTransitionTime: metav1.Now(),
520+
Severity: "Error",
521+
Reason: "ErrorMessage",
522+
Message: "PreAllocation and requested ip address are conflicting",
523+
})
524+
addressClaim.SetConditions(conditions)
525+
return "", 0, nil, errors.New("PreAllocation and requested ip address are conflicting")
526+
}
527+
488528
for _, pool := range m.IPPool.Spec.Pools {
489529
if ipAllocated {
490530
break
@@ -496,6 +536,13 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1.IPAddressCla
496536
break
497537
}
498538
index++
539+
// Check if requestedIP is present and matches the current address
540+
if requestedIP != "" && allocatedAddress != requestedIP {
541+
continue
542+
}
543+
if requestedIP != "" && allocatedAddress == requestedIP {
544+
isRequestedIPAllocated = true
545+
}
499546
// We have a pre-allocated ip, we just need to ensure that it matches the current address
500547
// if it does not, continue and try the next address
501548
if ipPreAllocated && allocatedAddress != preAllocatedAddress {
@@ -522,6 +569,20 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1.IPAddressCla
522569
}
523570
}
524571
}
572+
// We did not get requestedIp as it did not match with any available IP
573+
if requestedIP != "" && isRequestedIPAllocated && !ipAllocated {
574+
conditions := clusterv1.Conditions{}
575+
conditions = append(conditions, clusterv1.Condition{
576+
Type: "ErrorMessage",
577+
Status: corev1.ConditionTrue,
578+
LastTransitionTime: metav1.Now(),
579+
Severity: "Error",
580+
Reason: "ErrorMessage",
581+
Message: "Requested IP not available",
582+
})
583+
addressClaim.SetConditions(conditions)
584+
return "", 0, nil, errors.New("Requested IP not available")
585+
}
525586
// We have a preallocated IP but we did not find it in the pools! It means it is
526587
// misconfigured
527588
if !ipAllocated && ipPreAllocated {

ipam/ippool_manager_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,125 @@ var _ = Describe("IPPool manager", func() {
19161916
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
19171917
expectedPrefix: 24,
19181918
}),
1919+
Entry("One pool, with start and existing address, ipAddress annotation present", testCaseAllocateAddress{
1920+
ipPool: &ipamv1.IPPool{
1921+
Spec: ipamv1.IPPoolSpec{
1922+
Pools: []ipamv1.Pool{
1923+
{
1924+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
1925+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
1926+
},
1927+
},
1928+
Prefix: 24,
1929+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
1930+
},
1931+
},
1932+
ipClaim: &ipamv1.IPClaim{
1933+
ObjectMeta: metav1.ObjectMeta{
1934+
Name: "TestRef",
1935+
Annotations: map[string]string{
1936+
IPAddressAnnotation: "192.168.0.16",
1937+
},
1938+
},
1939+
},
1940+
addresses: map[ipamv1.IPAddressStr]string{
1941+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
1942+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
1943+
},
1944+
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
1945+
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
1946+
expectedPrefix: 24,
1947+
}),
1948+
Entry("One pool, with start and existing address, ipAddress annotation present but already acquired", testCaseAllocateAddress{
1949+
ipPool: &ipamv1.IPPool{
1950+
Spec: ipamv1.IPPoolSpec{
1951+
Pools: []ipamv1.Pool{
1952+
{
1953+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
1954+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
1955+
},
1956+
},
1957+
Prefix: 24,
1958+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
1959+
},
1960+
},
1961+
ipClaim: &ipamv1.IPClaim{
1962+
ObjectMeta: metav1.ObjectMeta{
1963+
Name: "TestRef",
1964+
Annotations: map[string]string{
1965+
IPAddressAnnotation: "192.168.0.11",
1966+
},
1967+
},
1968+
},
1969+
addresses: map[ipamv1.IPAddressStr]string{
1970+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
1971+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
1972+
},
1973+
expectError: true,
1974+
}),
1975+
1976+
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with conflicting ipclaim name", testCaseAllocateAddress{
1977+
ipPool: &ipamv1.IPPool{
1978+
Spec: ipamv1.IPPoolSpec{
1979+
Pools: []ipamv1.Pool{
1980+
{
1981+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
1982+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
1983+
},
1984+
},
1985+
Prefix: 24,
1986+
PreAllocations: map[string]ipamv1.IPAddressStr{
1987+
"TestRef": ipamv1.IPAddressStr("192.168.0.15"),
1988+
},
1989+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
1990+
},
1991+
},
1992+
ipClaim: &ipamv1.IPClaim{
1993+
ObjectMeta: metav1.ObjectMeta{
1994+
Name: "TestRef",
1995+
Annotations: map[string]string{
1996+
IPAddressAnnotation: "192.168.0.16",
1997+
},
1998+
},
1999+
},
2000+
addresses: map[ipamv1.IPAddressStr]string{
2001+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
2002+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
2003+
},
2004+
expectError: true,
2005+
}),
2006+
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with non-conflicting ipclaim name", testCaseAllocateAddress{
2007+
ipPool: &ipamv1.IPPool{
2008+
Spec: ipamv1.IPPoolSpec{
2009+
Pools: []ipamv1.Pool{
2010+
{
2011+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
2012+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
2013+
},
2014+
},
2015+
Prefix: 24,
2016+
PreAllocations: map[string]ipamv1.IPAddressStr{
2017+
"TestRef": ipamv1.IPAddressStr("192.168.0.16"),
2018+
},
2019+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2020+
},
2021+
},
2022+
ipClaim: &ipamv1.IPClaim{
2023+
ObjectMeta: metav1.ObjectMeta{
2024+
Name: "TestRef",
2025+
Annotations: map[string]string{
2026+
IPAddressAnnotation: "192.168.0.16",
2027+
},
2028+
},
2029+
},
2030+
addresses: map[ipamv1.IPAddressStr]string{
2031+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
2032+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
2033+
},
2034+
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
2035+
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2036+
expectedPrefix: 24,
2037+
}),
19192038
Entry("One pool, with subnet and override prefix", testCaseAllocateAddress{
19202039
ipPool: &ipamv1.IPPool{
19212040
Spec: ipamv1.IPPoolSpec{
@@ -2230,6 +2349,124 @@ var _ = Describe("IPPool manager", func() {
22302349
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
22312350
expectedPrefix: 24,
22322351
}),
2352+
Entry("One pool, with start and existing address, ipAddress annotation", testCapiCaseAllocateAddress{
2353+
ipPool: &ipamv1.IPPool{
2354+
Spec: ipamv1.IPPoolSpec{
2355+
Pools: []ipamv1.Pool{
2356+
{
2357+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
2358+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
2359+
},
2360+
},
2361+
Prefix: 24,
2362+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2363+
},
2364+
},
2365+
ipAddressClaim: &capipamv1.IPAddressClaim{
2366+
ObjectMeta: metav1.ObjectMeta{
2367+
Name: "TestRef",
2368+
Annotations: map[string]string{
2369+
IPAddressAnnotation: "192.168.0.16",
2370+
},
2371+
},
2372+
},
2373+
addresses: map[ipamv1.IPAddressStr]string{
2374+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
2375+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
2376+
},
2377+
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
2378+
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2379+
expectedPrefix: 24,
2380+
}),
2381+
Entry("One pool, with start and existing address, ipAddress annotation but already acquired", testCapiCaseAllocateAddress{
2382+
ipPool: &ipamv1.IPPool{
2383+
Spec: ipamv1.IPPoolSpec{
2384+
Pools: []ipamv1.Pool{
2385+
{
2386+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
2387+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
2388+
},
2389+
},
2390+
Prefix: 24,
2391+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2392+
},
2393+
},
2394+
ipAddressClaim: &capipamv1.IPAddressClaim{
2395+
ObjectMeta: metav1.ObjectMeta{
2396+
Name: "TestRef",
2397+
Annotations: map[string]string{
2398+
IPAddressAnnotation: "192.168.0.12",
2399+
},
2400+
},
2401+
},
2402+
addresses: map[ipamv1.IPAddressStr]string{
2403+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
2404+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
2405+
},
2406+
expectError: true,
2407+
}),
2408+
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with conflicting ipclaim name", testCapiCaseAllocateAddress{
2409+
ipPool: &ipamv1.IPPool{
2410+
Spec: ipamv1.IPPoolSpec{
2411+
Pools: []ipamv1.Pool{
2412+
{
2413+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
2414+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
2415+
},
2416+
},
2417+
Prefix: 24,
2418+
PreAllocations: map[string]ipamv1.IPAddressStr{
2419+
"TestRef": ipamv1.IPAddressStr("192.168.0.15"),
2420+
},
2421+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2422+
},
2423+
},
2424+
ipAddressClaim: &capipamv1.IPAddressClaim{
2425+
ObjectMeta: metav1.ObjectMeta{
2426+
Name: "TestRef",
2427+
Annotations: map[string]string{
2428+
IPAddressAnnotation: "192.168.0.16",
2429+
},
2430+
},
2431+
},
2432+
addresses: map[ipamv1.IPAddressStr]string{
2433+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
2434+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
2435+
},
2436+
expectError: true,
2437+
}),
2438+
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with non-conflicting ipclaim name", testCapiCaseAllocateAddress{
2439+
ipPool: &ipamv1.IPPool{
2440+
Spec: ipamv1.IPPoolSpec{
2441+
Pools: []ipamv1.Pool{
2442+
{
2443+
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
2444+
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
2445+
},
2446+
},
2447+
Prefix: 24,
2448+
PreAllocations: map[string]ipamv1.IPAddressStr{
2449+
"TestRef": ipamv1.IPAddressStr("192.168.0.16"),
2450+
},
2451+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2452+
},
2453+
},
2454+
ipAddressClaim: &capipamv1.IPAddressClaim{
2455+
ObjectMeta: metav1.ObjectMeta{
2456+
Name: "TestRef",
2457+
Annotations: map[string]string{
2458+
IPAddressAnnotation: "192.168.0.16",
2459+
},
2460+
},
2461+
},
2462+
addresses: map[ipamv1.IPAddressStr]string{
2463+
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
2464+
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
2465+
},
2466+
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
2467+
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
2468+
expectedPrefix: 24,
2469+
}),
22332470
Entry("One pool, with subnet and override prefix", testCapiCaseAllocateAddress{
22342471
ipPool: &ipamv1.IPPool{
22352472
Spec: ipamv1.IPPoolSpec{

0 commit comments

Comments
 (0)