@@ -16,14 +16,17 @@ package clientv2
16
16
17
17
import (
18
18
"context"
19
+ "fmt"
19
20
"net"
21
+ "time"
22
+
23
+ log "github.com/sirupsen/logrus"
20
24
21
25
apiv2 "github.com/projectcalico/libcalico-go/lib/apis/v2"
22
26
cerrors "github.com/projectcalico/libcalico-go/lib/errors"
23
27
cnet "github.com/projectcalico/libcalico-go/lib/net"
24
28
"github.com/projectcalico/libcalico-go/lib/options"
25
29
"github.com/projectcalico/libcalico-go/lib/watch"
26
- log "github.com/sirupsen/logrus"
27
30
)
28
31
29
32
// IPPoolInterface has methods to work with IPPool resources.
@@ -45,7 +48,7 @@ type ipPools struct {
45
48
// representation of the IPPool, and an error, if there is any.
46
49
func (r ipPools ) Create (ctx context.Context , res * apiv2.IPPool , opts options.SetOptions ) (* apiv2.IPPool , error ) {
47
50
// Validate the IPPool before creating the resource.
48
- if err := r .validateAndSetDefaults (res ); err != nil {
51
+ if err := r .validateAndSetDefaults (ctx , res , nil ); err != nil {
49
52
return nil , err
50
53
}
51
54
@@ -67,14 +70,20 @@ func (r ipPools) Create(ctx context.Context, res *apiv2.IPPool, opts options.Set
67
70
// Update takes the representation of a IPPool and updates it. Returns the stored
68
71
// representation of the IPPool, and an error, if there is any.
69
72
func (r ipPools ) Update (ctx context.Context , res * apiv2.IPPool , opts options.SetOptions ) (* apiv2.IPPool , error ) {
73
+ // Get the existing settings, so that we can validate the CIDR has not changed.
74
+ old , err := r .Get (ctx , res .Name , options.GetOptions {})
75
+ if err != nil {
76
+ return nil , err
77
+ }
78
+
70
79
// Validate the IPPool updating the resource.
71
- if err := r .validateAndSetDefaults (res ); err != nil {
80
+ if err := r .validateAndSetDefaults (ctx , res , old ); err != nil {
72
81
return nil , err
73
82
}
74
83
75
84
// Enable IPIP globally if required. Do this before the Update so if it fails the user
76
85
// can retry the same command.
77
- err : = r .maybeEnableIPIP (ctx , res )
86
+ err = r .maybeEnableIPIP (ctx , res )
78
87
if err != nil {
79
88
return nil , err
80
89
}
@@ -88,6 +97,65 @@ func (r ipPools) Update(ctx context.Context, res *apiv2.IPPool, opts options.Set
88
97
89
98
// Delete takes name of the IPPool and deletes it. Returns an error if one occurs.
90
99
func (r ipPools ) Delete (ctx context.Context , name string , opts options.DeleteOptions ) (* apiv2.IPPool , error ) {
100
+ // Deleting a pool requires a little care because of existing endpoints
101
+ // using IP addresses allocated in the pool. We do the deletion in
102
+ // the following steps:
103
+ // - disable the pool so no more IPs are assigned from it
104
+ // - remove all affinities associated with the pool
105
+ // - delete the pool
106
+
107
+ // Get the pool so that we can find the CIDR associated with it.
108
+ pool , err := r .Get (ctx , name , options.GetOptions {})
109
+ if err != nil {
110
+ return nil , err
111
+ }
112
+
113
+ logCxt := log .WithFields (log.Fields {
114
+ "CIDR" : pool .Spec .CIDR ,
115
+ "Name" : name ,
116
+ })
117
+
118
+ // If the pool is active, set the disabled flag to ensure we stop allocating from this pool.
119
+ if ! pool .Spec .Disabled {
120
+ logCxt .Info ("Disabling pool to release affinities" )
121
+ pool .Spec .Disabled = true
122
+
123
+ // If the Delete has been called with a ResourceVersion then use that to perform the
124
+ // update - that way we'll catch update conflicts (we could actually check here, but
125
+ // the most likely scenario is there isn't one - so just pass it through and let the
126
+ // Update handle any conflicts).
127
+ if opts .ResourceVersion != "" {
128
+ pool .ResourceVersion = opts .ResourceVersion
129
+ }
130
+ if _ , err := r .Update (ctx , pool , options.SetOptions {}); err != nil {
131
+ return nil , err
132
+ }
133
+
134
+ // Reset the resource version before the actual delete since the version of that resource
135
+ // will now have been updated.
136
+ opts .ResourceVersion = ""
137
+ }
138
+
139
+ // Release affinities associated with this pool. We do this even if the pool was disabled
140
+ // (since it may have been enabled at one time, and if there are no affine blocks created
141
+ // then this will be a no-op). We've already validated the CIDR so we know it will parse.
142
+ if _ , cidrNet , err := cnet .ParseCIDR (pool .Spec .CIDR ); err == nil {
143
+ logCxt .Info ("Releasing pool affinities" )
144
+
145
+ // Pause for a short period before releasing the affinities - this gives any in-progress
146
+ // allocations an opportunity to finish.
147
+ time .Sleep (500 * time .Millisecond )
148
+ err = r .client .IPAM ().ReleasePoolAffinities (ctx , * cidrNet )
149
+
150
+ // Depending on the datastore, IPAM may not be supported. If we get a not supported
151
+ // error, then continue. Any other error, fail.
152
+ if _ , ok := err .(cerrors.ErrorOperationNotSupported ); ! ok && err != nil {
153
+ return nil , err
154
+ }
155
+ }
156
+
157
+ // And finally, delete the pool.
158
+ logCxt .Info ("Deleting pool" )
91
159
out , err := r .client .resources .Delete (ctx , opts , apiv2 .KindIPPool , noNamespace , name )
92
160
if out != nil {
93
161
return out .(* apiv2.IPPool ), err
@@ -120,12 +188,14 @@ func (r ipPools) Watch(ctx context.Context, opts options.ListOptions) (watch.Int
120
188
return r .client .resources .Watch (ctx , opts , apiv2 .KindIPPool )
121
189
}
122
190
123
- // validateAndSetDefaults validates IPPool fields.
124
- func (_ ipPools ) validateAndSetDefaults (pool * apiv2.IPPool ) error {
191
+ // validateAndSetDefaults validates IPPool fields and sets default values that are
192
+ // not assigned.
193
+ // The old pool will be unassigned for a Create.
194
+ func (r ipPools ) validateAndSetDefaults (ctx context.Context , new , old * apiv2.IPPool ) error {
125
195
errFields := []cerrors.ErroredField {}
126
196
127
197
// Spec.CIDR field must not be empty.
128
- if pool .Spec .CIDR == "" {
198
+ if new .Spec .CIDR == "" {
129
199
return cerrors.ErrorValidation {
130
200
ErroredFields : []cerrors.ErroredField {{
131
201
Name : "IPPool.Spec.CIDR" ,
@@ -135,55 +205,103 @@ func (_ ipPools) validateAndSetDefaults(pool *apiv2.IPPool) error {
135
205
}
136
206
137
207
// Make sure the CIDR is parsable.
138
- ipAddr , cidr , err := cnet .ParseCIDR (pool .Spec .CIDR )
208
+ ipAddr , cidr , err := cnet .ParseCIDR (new .Spec .CIDR )
139
209
if err != nil {
140
210
return cerrors.ErrorValidation {
141
211
ErroredFields : []cerrors.ErroredField {{
142
212
Name : "IPPool.Spec.CIDR" ,
143
213
Reason : "IPPool CIDR must be a valid subnet" ,
214
+ Value : new .Spec .CIDR ,
144
215
}},
145
216
}
146
217
}
147
218
219
+ // Normalize the CIDR before persisting.
220
+ new .Spec .CIDR = cidr .String ()
221
+
222
+ // If there was a previous pool then this must be an Update, validate that the
223
+ // CIDR has not changed. Since we are using normalized CIDRs we can just do a
224
+ // simple string comparison.
225
+ if old != nil && old .Spec .CIDR != new .Spec .CIDR {
226
+ errFields = append (errFields , cerrors.ErroredField {
227
+ Name : "IPPool.Spec.CIDR" ,
228
+ Reason : "IPPool CIDR cannot be modified" ,
229
+ Value : new .Spec .CIDR ,
230
+ })
231
+ }
232
+
233
+ // If there was no previous pool then this must be a Create. Check that the CIDR
234
+ // does not overlap with any other pool CIDRs.
235
+ if old == nil {
236
+ allPools , err := r .List (ctx , options.ListOptions {})
237
+ if err != nil {
238
+ return err
239
+ }
240
+
241
+ for _ , otherPool := range allPools .Items {
242
+ // It's possible that Create is called for a pre-existing pool, so skip our own
243
+ // pool and let the generic processing handle the pre-existing resource error case.
244
+ if otherPool .Name == new .Name {
245
+ continue
246
+ }
247
+ _ , otherCIDR , err := cnet .ParseCIDR (otherPool .Spec .CIDR )
248
+ if err != nil {
249
+ log .WithField ("Name" , otherPool .Name ).WithError (err ).Error ("IPPool is configured with an invalid CIDR" )
250
+ continue
251
+ }
252
+ if otherCIDR .IsNetOverlap (cidr .IPNet ) {
253
+ errFields = append (errFields , cerrors.ErroredField {
254
+ Name : "IPPool.Spec.CIDR" ,
255
+ Reason : fmt .Sprintf ("IPPool(%s) CIDR overlaps with IPPool(%s) CIDR %s" , new .Name , otherPool .Name , otherPool .Spec .CIDR ),
256
+ Value : new .Spec .CIDR ,
257
+ })
258
+ }
259
+ }
260
+ }
261
+
148
262
// Make sure IPIPMode is defaulted to "Never".
149
- if len (pool .Spec .IPIPMode ) == 0 {
150
- pool .Spec .IPIPMode = apiv2 .IPIPModeNever
263
+ if len (new .Spec .IPIPMode ) == 0 {
264
+ new .Spec .IPIPMode = apiv2 .IPIPModeNever
151
265
}
152
266
153
267
// IPIP cannot be enabled for IPv6.
154
- if cidr .Version () == 6 && pool .Spec .IPIPMode != apiv2 .IPIPModeNever {
268
+ if cidr .Version () == 6 && new .Spec .IPIPMode != apiv2 .IPIPModeNever {
155
269
errFields = append (errFields , cerrors.ErroredField {
156
- Name : "IPPool.Spec.IPIP.Mode " ,
270
+ Name : "IPPool.Spec.IPIPMode " ,
157
271
Reason : "IPIP is not supported on an IPv6 IP pool" ,
272
+ Value : new .Spec .IPIPMode ,
158
273
})
159
274
}
160
275
161
276
// The Calico IPAM places restrictions on the minimum IP pool size. If
162
277
// the ippool is enabled, check that the pool is at least the minimum size.
163
- if ! pool .Spec .Disabled {
278
+ if ! new .Spec .Disabled {
164
279
ones , bits := cidr .Mask .Size ()
165
280
log .Debugf ("Pool CIDR: %s, num bits: %d" , cidr .String (), bits - ones )
166
281
if bits - ones < 6 {
167
282
if cidr .Version () == 4 {
168
283
errFields = append (errFields , cerrors.ErroredField {
169
284
Name : "IPPool.Spec.CIDR" ,
170
285
Reason : "IPv4 pool size is too small (min /26) for use with Calico IPAM" ,
286
+ Value : new .Spec .CIDR ,
171
287
})
172
288
} else {
173
289
errFields = append (errFields , cerrors.ErroredField {
174
290
Name : "IPPool.Spec.CIDR" ,
175
291
Reason : "IPv6 pool size is too small (min /122) for use with Calico IPAM" ,
292
+ Value : new .Spec .CIDR ,
176
293
})
177
294
}
178
295
}
179
296
}
180
297
181
298
// The Calico CIDR should be strictly masked
182
- log .Debugf ("IPPool CIDR: %s, Masked IP: %d" , pool .Spec .CIDR , cidr .IP )
299
+ log .Debugf ("IPPool CIDR: %s, Masked IP: %d" , new .Spec .CIDR , cidr .IP )
183
300
if cidr .IP .String () != ipAddr .String () {
184
301
errFields = append (errFields , cerrors.ErroredField {
185
302
Name : "IPPool.Spec.CIDR" ,
186
- Reason : "IP pool CIDR is not strictly masked" ,
303
+ Reason : "IPPool CIDR is not strictly masked" ,
304
+ Value : new .Spec .CIDR ,
187
305
})
188
306
}
189
307
@@ -202,14 +320,16 @@ func (_ ipPools) validateAndSetDefaults(pool *apiv2.IPPool) error {
202
320
if cidr .Version () == 4 && cidr .IsNetOverlap (ipv4LinkLocalNet ) {
203
321
errFields = append (errFields , cerrors.ErroredField {
204
322
Name : "IPPool.Spec.CIDR" ,
205
- Reason : "IP pool range overlaps with IPv4 Link Local range 169.254.0.0/16" ,
323
+ Reason : "IPPool CIDR overlaps with IPv4 Link Local range 169.254.0.0/16" ,
324
+ Value : new .Spec .CIDR ,
206
325
})
207
326
}
208
327
209
328
if cidr .Version () == 6 && cidr .IsNetOverlap (ipv6LinkLocalNet ) {
210
329
errFields = append (errFields , cerrors.ErroredField {
211
330
Name : "IPPool.Spec.CIDR" ,
212
- Reason : "IP pool range overlaps with IPv6 Link Local range fe80::/10" ,
331
+ Reason : "IPPool CIDR overlaps with IPv6 Link Local range fe80::/10" ,
332
+ Value : new .Spec .CIDR ,
213
333
})
214
334
}
215
335
0 commit comments