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
4 changes: 4 additions & 0 deletions examples/ippool/ippool.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ apiVersion: ipam.metal3.io/v1alpha1
kind: IPClaim
metadata:
name: ${CLUSTER_NAME}-controlplane-template-0-provisioning-pool
annotations:
ipAddress: <optional-annotation-for-specific-ip-request>
spec:
pool:
name: pool1
Expand All @@ -44,6 +46,8 @@ apiVersion: ipam.cluster.x-k8s.io/v1beta1
kind: IPAddressClaim
metadata:
name: ${CLUSTER_NAME}-controlplane-template-1-provisioning-pool
annotations:
ipAddress: <optional-annotation-for-specific-ip-request>
spec:
poolRef:
apiGroup: ipam.metal3.io
Expand Down
61 changes: 61 additions & 0 deletions ipam/ippool_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var (
const (
IPAddressClaimFinalizer = "ipam.metal3.io/ipaddressclaim"
IPAddressFinalizer = "ipam.metal3.io/ipaddress"
IPAddressAnnotation = "ipAddress"
)

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

ipAllocated := false

requestedIP := ipamv1.IPAddressStr(addressClaim.ObjectMeta.Annotations[IPAddressAnnotation])
isRequestedIPAllocated := false

// Conflict-case, claim is preAllocated but has requested different IP
if requestedIP != "" && ipPreAllocated && requestedIP != preAllocatedAddress {
addressClaim.Status.ErrorMessage = ptr.To("PreAllocation and requested ip address are conflicting")
return "", 0, nil, []ipamv1.IPAddressStr{}, errors.New("PreAllocation and requested ip address are conflicting")
}

for _, pool := range m.IPPool.Spec.Pools {
if ipAllocated {
break
Expand All @@ -426,6 +436,13 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
break
}
index++
// Check if requestedIP is present and matches the current address
if requestedIP != "" && allocatedAddress != requestedIP {
continue
}
if requestedIP != "" && allocatedAddress == requestedIP {
isRequestedIPAllocated = true
}
// We have a pre-allocated ip, we just need to ensure that it matches the current address
// if it does not, continue and try the next address
if ipPreAllocated && allocatedAddress != preAllocatedAddress {
Expand Down Expand Up @@ -455,6 +472,11 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
}
}
}
// We did not get requestedIp as it did not match with any available IP
if requestedIP != "" && isRequestedIPAllocated && !ipAllocated {
addressClaim.Status.ErrorMessage = ptr.To("Requested IP not available")
return "", 0, nil, []ipamv1.IPAddressStr{}, errors.New("Requested IP not available")
}
// We have a preallocated IP but we did not find it in the pools! It means it is
// misconfigured
if !ipAllocated && ipPreAllocated {
Expand Down Expand Up @@ -485,6 +507,24 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1.IPAddressCla

ipAllocated := false

requestedIP := ipamv1.IPAddressStr(addressClaim.ObjectMeta.Annotations[IPAddressAnnotation])
isRequestedIPAllocated := false

// Conflict-case, claim is preAllocated but has requested different IP
if requestedIP != "" && ipPreAllocated && requestedIP != preAllocatedAddress {
conditions := clusterv1.Conditions{}
conditions = append(conditions, clusterv1.Condition{
Type: "ErrorMessage",
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Severity: "Error",
Reason: "ErrorMessage",
Message: "PreAllocation and requested ip address are conflicting",
})
addressClaim.SetConditions(conditions)
return "", 0, nil, errors.New("PreAllocation and requested ip address are conflicting")
}

for _, pool := range m.IPPool.Spec.Pools {
if ipAllocated {
break
Expand All @@ -496,6 +536,13 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1.IPAddressCla
break
}
index++
// Check if requestedIP is present and matches the current address
if requestedIP != "" && allocatedAddress != requestedIP {
continue
}
if requestedIP != "" && allocatedAddress == requestedIP {
isRequestedIPAllocated = true
}
// We have a pre-allocated ip, we just need to ensure that it matches the current address
// if it does not, continue and try the next address
if ipPreAllocated && allocatedAddress != preAllocatedAddress {
Expand All @@ -522,6 +569,20 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1.IPAddressCla
}
}
}
// We did not get requestedIp as it did not match with any available IP
if requestedIP != "" && isRequestedIPAllocated && !ipAllocated {
conditions := clusterv1.Conditions{}
conditions = append(conditions, clusterv1.Condition{
Type: "ErrorMessage",
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Severity: "Error",
Reason: "ErrorMessage",
Message: "Requested IP not available",
})
addressClaim.SetConditions(conditions)
return "", 0, nil, errors.New("Requested IP not available")
}
// We have a preallocated IP but we did not find it in the pools! It means it is
// misconfigured
if !ipAllocated && ipPreAllocated {
Expand Down
237 changes: 237 additions & 0 deletions ipam/ippool_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,125 @@ var _ = Describe("IPPool manager", func() {
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
expectedPrefix: 24,
}),
Entry("One pool, with start and existing address, ipAddress annotation present", testCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipClaim: &ipamv1.IPClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.16",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
expectedPrefix: 24,
}),
Entry("One pool, with start and existing address, ipAddress annotation present but already acquired", testCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipClaim: &ipamv1.IPClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.11",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectError: true,
}),

Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with conflicting ipclaim name", testCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
PreAllocations: map[string]ipamv1.IPAddressStr{
"TestRef": ipamv1.IPAddressStr("192.168.0.15"),
},
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipClaim: &ipamv1.IPClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.16",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectError: true,
}),
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with non-conflicting ipclaim name", testCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
PreAllocations: map[string]ipamv1.IPAddressStr{
"TestRef": ipamv1.IPAddressStr("192.168.0.16"),
},
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipClaim: &ipamv1.IPClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.16",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
expectedPrefix: 24,
}),
Entry("One pool, with subnet and override prefix", testCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Expand Down Expand Up @@ -2230,6 +2349,124 @@ var _ = Describe("IPPool manager", func() {
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
expectedPrefix: 24,
}),
Entry("One pool, with start and existing address, ipAddress annotation", testCapiCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.16",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
expectedPrefix: 24,
}),
Entry("One pool, with start and existing address, ipAddress annotation but already acquired", testCapiCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.12",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectError: true,
}),
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with conflicting ipclaim name", testCapiCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
PreAllocations: map[string]ipamv1.IPAddressStr{
"TestRef": ipamv1.IPAddressStr("192.168.0.15"),
},
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.16",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectError: true,
}),
Entry("One pool, with start and existing address, ipAddress annotation present and requested ipAddress in preAllocations with non-conflicting ipclaim name", testCapiCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Pools: []ipamv1.Pool{
{
Start: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.11")),
End: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.20")),
},
},
Prefix: 24,
PreAllocations: map[string]ipamv1.IPAddressStr{
"TestRef": ipamv1.IPAddressStr("192.168.0.16"),
},
Gateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
},
},
ipAddressClaim: &capipamv1.IPAddressClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "TestRef",
Annotations: map[string]string{
IPAddressAnnotation: "192.168.0.16",
},
},
},
addresses: map[ipamv1.IPAddressStr]string{
ipamv1.IPAddressStr("192.168.0.12"): "bcde",
ipamv1.IPAddressStr("192.168.0.11"): "abcd",
},
expectedAddress: ipamv1.IPAddressStr("192.168.0.16"),
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
expectedPrefix: 24,
}),
Entry("One pool, with subnet and override prefix", testCapiCaseAllocateAddress{
ipPool: &ipamv1.IPPool{
Spec: ipamv1.IPPoolSpec{
Expand Down