Skip to content

Commit a1958aa

Browse files
Merge pull request #29 from Nordix/fix/ip_allocation_feruz
🐛 Fix issue with pre-allocated addresses
2 parents b276c4f + 9eb06d5 commit a1958aa

File tree

6 files changed

+574
-295
lines changed

6 files changed

+574
-295
lines changed

api/v1alpha1/ippool_webhook.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,78 @@ func (c *IPPool) ValidateUpdate(old runtime.Object) error {
6161
),
6262
)
6363
}
64+
allocationOutOfBonds, inUseOutOfBonds := c.checkPoolBonds(oldM3ipp)
65+
if len(allocationOutOfBonds) != 0 {
66+
for _, address := range allocationOutOfBonds {
67+
allErrs = append(allErrs,
68+
field.Invalid(
69+
field.NewPath("spec", "preAllocations"),
70+
address,
71+
"is out of bonds of the pools given",
72+
),
73+
)
74+
}
75+
}
76+
if len(inUseOutOfBonds) != 0 {
77+
for _, address := range inUseOutOfBonds {
78+
allErrs = append(allErrs,
79+
field.Invalid(
80+
field.NewPath("spec", "pools"),
81+
address,
82+
"is in use but out of bonds of the pools given",
83+
),
84+
)
85+
}
86+
}
6487

6588
if len(allErrs) == 0 {
6689
return nil
6790
}
6891
return apierrors.NewInvalid(GroupVersion.WithKind("Metal3Data").GroupKind(), c.Name, allErrs)
6992
}
7093

94+
func (c *IPPool) checkPoolBonds(old *IPPool) ([]IPAddressStr, []IPAddressStr) {
95+
allocationOutOfBonds := []IPAddressStr{}
96+
inUseOutOfBonds := []IPAddressStr{}
97+
for _, address := range c.Spec.PreAllocations {
98+
inBonds := c.isAddressInBonds(address)
99+
100+
if !inBonds {
101+
allocationOutOfBonds = append(allocationOutOfBonds, address)
102+
}
103+
}
104+
for _, address := range old.Status.Allocations {
105+
inBonds := c.isAddressInBonds(address)
106+
107+
if !inBonds {
108+
inUseOutOfBonds = append(inUseOutOfBonds, address)
109+
}
110+
}
111+
return allocationOutOfBonds, inUseOutOfBonds
112+
}
113+
114+
func (c *IPPool) isAddressInBonds(address IPAddressStr) bool {
115+
inBonds := false
116+
for _, pool := range c.Spec.Pools {
117+
if inBonds {
118+
break
119+
}
120+
index := 0
121+
for !inBonds {
122+
allocatedAddress, err := GetIPAddress(pool, index)
123+
if err != nil {
124+
break
125+
}
126+
index++
127+
if allocatedAddress == address {
128+
inBonds = true
129+
break
130+
}
131+
}
132+
}
133+
return inBonds
134+
}
135+
71136
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
72137
func (c *IPPool) ValidateDelete() error {
73138
return nil

api/v1alpha1/ippool_webhook_test.go

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,33 +71,100 @@ func TestIPPoolValidation(t *testing.T) {
7171

7272
func TestIPPoolUpdateValidation(t *testing.T) {
7373

74+
startAddr := IPAddressStr("192.168.0.1")
75+
endAddr := IPAddressStr("192.168.0.10")
76+
7477
tests := []struct {
75-
name string
76-
expectErr bool
77-
newPool *IPPoolSpec
78-
oldPool *IPPoolSpec
78+
name string
79+
expectErr bool
80+
newPoolSpec *IPPoolSpec
81+
oldPoolSpec *IPPoolSpec
82+
oldPoolStatus IPPoolStatus
7983
}{
8084
{
81-
name: "should succeed when values and templates correct",
82-
expectErr: false,
83-
newPool: &IPPoolSpec{},
84-
oldPool: &IPPoolSpec{},
85+
name: "should succeed when values and templates correct",
86+
expectErr: false,
87+
newPoolSpec: &IPPoolSpec{},
88+
oldPoolSpec: &IPPoolSpec{},
8589
},
8690
{
87-
name: "should fail when oldPool is nil",
88-
expectErr: true,
89-
newPool: &IPPoolSpec{},
90-
oldPool: nil,
91+
name: "should fail when oldPoolSpec is nil",
92+
expectErr: true,
93+
newPoolSpec: &IPPoolSpec{},
94+
oldPoolSpec: nil,
9195
},
9296
{
9397
name: "should fail when namePrefix value changes",
9498
expectErr: true,
95-
newPool: &IPPoolSpec{
99+
newPoolSpec: &IPPoolSpec{
96100
NamePrefix: "abcde",
97101
},
98-
oldPool: &IPPoolSpec{
102+
oldPoolSpec: &IPPoolSpec{
103+
NamePrefix: "abcd",
104+
},
105+
},
106+
{
107+
name: "should succeed when preAllocations are correct",
108+
expectErr: false,
109+
newPoolSpec: &IPPoolSpec{
110+
NamePrefix: "abcd",
111+
Pools: []Pool{
112+
{Start: &startAddr, End: &endAddr},
113+
},
114+
PreAllocations: map[string]IPAddressStr{
115+
"alloc": IPAddressStr("192.168.0.2"),
116+
},
117+
},
118+
oldPoolSpec: &IPPoolSpec{
99119
NamePrefix: "abcd",
100120
},
121+
oldPoolStatus: IPPoolStatus{
122+
Allocations: map[string]IPAddressStr{
123+
"inuse": IPAddressStr("192.168.0.3"),
124+
},
125+
},
126+
},
127+
{
128+
name: "should fail when preAllocations are incorrect",
129+
expectErr: true,
130+
newPoolSpec: &IPPoolSpec{
131+
NamePrefix: "abcd",
132+
Pools: []Pool{
133+
{Start: &startAddr, End: &endAddr},
134+
},
135+
PreAllocations: map[string]IPAddressStr{
136+
"alloc": IPAddressStr("192.168.0.20"),
137+
},
138+
},
139+
oldPoolSpec: &IPPoolSpec{
140+
NamePrefix: "abcd",
141+
},
142+
oldPoolStatus: IPPoolStatus{
143+
Allocations: map[string]IPAddressStr{
144+
"inuse": IPAddressStr("192.168.0.3"),
145+
},
146+
},
147+
},
148+
{
149+
name: "should fail when ip in use",
150+
expectErr: true,
151+
newPoolSpec: &IPPoolSpec{
152+
NamePrefix: "abcd",
153+
Pools: []Pool{
154+
{Start: &startAddr, End: &endAddr},
155+
},
156+
PreAllocations: map[string]IPAddressStr{
157+
"alloc": IPAddressStr("192.168.0.2"),
158+
},
159+
},
160+
oldPoolSpec: &IPPoolSpec{
161+
NamePrefix: "abcd",
162+
},
163+
oldPoolStatus: IPPoolStatus{
164+
Allocations: map[string]IPAddressStr{
165+
"inuse": IPAddressStr("192.168.0.30"),
166+
},
167+
},
101168
},
102169
}
103170

@@ -109,15 +176,16 @@ func TestIPPoolUpdateValidation(t *testing.T) {
109176
ObjectMeta: metav1.ObjectMeta{
110177
Namespace: "foo",
111178
},
112-
Spec: *tt.newPool,
179+
Spec: *tt.newPoolSpec,
113180
}
114181

115-
if tt.oldPool != nil {
182+
if tt.oldPoolSpec != nil {
116183
oldPool = &IPPool{
117184
ObjectMeta: metav1.ObjectMeta{
118185
Namespace: "foo",
119186
},
120-
Spec: *tt.oldPool,
187+
Spec: *tt.oldPoolSpec,
188+
Status: tt.oldPoolStatus,
121189
}
122190
} else {
123191
oldPool = nil

api/v1alpha1/utils.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
"fmt"
21+
"math/big"
22+
"net"
23+
24+
"github.com/pkg/errors"
25+
)
26+
27+
// GetIPAddress renders the IP address, taking the index, offset and step into
28+
// account, it is IP version agnostic
29+
func GetIPAddress(entry Pool, index int) (IPAddressStr, error) {
30+
31+
if entry.Start == nil && entry.Subnet == nil {
32+
return "", errors.New("Either Start or Subnet is required for ipAddress")
33+
}
34+
var ip net.IP
35+
var err error
36+
var ipNet *net.IPNet
37+
offset := index
38+
39+
// If start is given, use it to add the offset
40+
if entry.Start != nil {
41+
var endIP net.IP
42+
if entry.End != nil {
43+
endIP = net.ParseIP(string(*entry.End))
44+
}
45+
ip, err = addOffsetToIP(net.ParseIP(string(*entry.Start)), endIP, offset)
46+
if err != nil {
47+
return "", err
48+
}
49+
50+
// Verify that the IP is in the subnet
51+
if entry.Subnet != nil {
52+
_, ipNet, err = net.ParseCIDR(string(*entry.Subnet))
53+
if err != nil {
54+
return "", err
55+
}
56+
if !ipNet.Contains(ip) {
57+
return "", errors.New("IP address out of bonds")
58+
}
59+
}
60+
61+
// If it is not given, use the CIDR ip address and increment the offset by 1
62+
} else {
63+
ip, ipNet, err = net.ParseCIDR(string(*entry.Subnet))
64+
if err != nil {
65+
return "", err
66+
}
67+
offset++
68+
ip, err = addOffsetToIP(ip, nil, offset)
69+
if err != nil {
70+
return "", err
71+
}
72+
73+
// Verify that the ip is in the subnet
74+
if !ipNet.Contains(ip) {
75+
return "", errors.New("IP address out of bonds")
76+
}
77+
}
78+
return IPAddressStr(ip.String()), nil
79+
}
80+
81+
// addOffsetToIP computes the value of the IP address with the offset. It is
82+
// IP version agnostic
83+
// Note that if the resulting IP address is in the format ::ffff:xxxx:xxxx then
84+
// ip.String will fail to select the correct type of ip
85+
func addOffsetToIP(ip, endIP net.IP, offset int) (net.IP, error) {
86+
ip4 := false
87+
//ip := net.ParseIP(ipString)
88+
if ip.To4() != nil {
89+
ip4 = true
90+
}
91+
92+
// Create big integers
93+
IPInt := big.NewInt(0)
94+
OffsetInt := big.NewInt(int64(offset))
95+
96+
// Transform the ip into an int. (big endian function)
97+
IPInt = IPInt.SetBytes(ip)
98+
99+
// add the two integers
100+
IPInt = IPInt.Add(IPInt, OffsetInt)
101+
102+
// return the bytes list
103+
IPBytes := IPInt.Bytes()
104+
105+
IPBytesLen := len(IPBytes)
106+
107+
// Verify that the IPv4 or IPv6 fulfills theirs constraints
108+
if (ip4 && IPBytesLen > 6 && IPBytes[4] != 255 && IPBytes[5] != 255) ||
109+
(!ip4 && IPBytesLen > 16) {
110+
return nil, errors.New(fmt.Sprintf("IP address overflow for : %s", ip.String()))
111+
}
112+
113+
//transform the end ip into an Int to compare
114+
if endIP != nil {
115+
endIPInt := big.NewInt(0)
116+
endIPInt = endIPInt.SetBytes(endIP)
117+
// Computed IP is higher than the end IP
118+
if IPInt.Cmp(endIPInt) > 0 {
119+
return nil, errors.New(fmt.Sprintf("IP address out of bonds for : %s", ip.String()))
120+
}
121+
}
122+
123+
// COpy the output back into an ip
124+
copy(ip[16-IPBytesLen:], IPBytes)
125+
return ip, nil
126+
}

0 commit comments

Comments
 (0)