@@ -38,6 +38,9 @@ import (
38
38
var (
39
39
SpotRequirement = scheduling .NewRequirements (scheduling .NewRequirement (v1 .CapacityTypeLabelKey , corev1 .NodeSelectorOpIn , v1 .CapacityTypeSpot ))
40
40
OnDemandRequirement = scheduling .NewRequirements (scheduling .NewRequirement (v1 .CapacityTypeLabelKey , corev1 .NodeSelectorOpIn , v1 .CapacityTypeOnDemand ))
41
+
42
+ TrueStaticAvailabilityResolver OfferingAvailabilityResolver = staticAvailabilityResolver {available : true }
43
+ FalseStaticAvailabilityResolver OfferingAvailabilityResolver = staticAvailabilityResolver {available : false }
41
44
)
42
45
43
46
type DriftReason string
@@ -224,6 +227,15 @@ func (its InstanceTypes) Truncate(requirements scheduling.Requirements, maxItems
224
227
return truncatedInstanceTypes , nil
225
228
}
226
229
230
+ func (its InstanceTypes ) Difference (other InstanceTypes ) InstanceTypes {
231
+ names := sets .New (lo .Map (other , func (it * InstanceType , _ int ) string {
232
+ return it .Name
233
+ })... )
234
+ return lo .Reject (its , func (it * InstanceType , _ int ) bool {
235
+ return names .Has (it .Name )
236
+ })
237
+ }
238
+
227
239
type InstanceTypeOverhead struct {
228
240
// KubeReserved returns the default resources allocated to kubernetes system daemons by default
229
241
KubeReserved corev1.ResourceList
@@ -237,24 +249,78 @@ func (i InstanceTypeOverhead) Total() corev1.ResourceList {
237
249
return resources .Merge (i .KubeReserved , i .SystemReserved , i .EvictionThreshold )
238
250
}
239
251
252
+ // An OfferingAvailabilityResolver is used to determine if there is available capacity for a given offering. To ensure
253
+ // consistency between multiple controllers attempting to provision a NodeClaim with a given offering, offerings should
254
+ // be "reserved" by the controller. Once a launch decision has been made, all offerings which were reserved may be
255
+ // released, enabling their use once again.
256
+ type OfferingAvailabilityResolver interface {
257
+ Available () bool
258
+ Reserve (string ) bool
259
+ GetReservation (string ) OfferingReservation
260
+ }
261
+
262
+ type OfferingReservation interface {
263
+ Release ()
264
+ Commit ()
265
+ Matches (* v1.NodeClaim ) bool
266
+ }
267
+
268
+ type OfferingReservations []OfferingReservation
269
+
270
+ func (r OfferingReservations ) Commit () {
271
+ for _ , reservation := range r {
272
+ reservation .Commit ()
273
+ }
274
+ }
275
+
276
+ func (r OfferingReservations ) Release () {
277
+ for _ , reservation := range r {
278
+ reservation .Release ()
279
+ }
280
+ }
281
+
282
+ func (r OfferingReservations ) Matching (nc * v1.NodeClaim ) OfferingReservations {
283
+ return lo .Filter (r , func (reservation OfferingReservation , _ int ) bool {
284
+ return reservation .Matches (nc )
285
+ })
286
+ }
287
+
288
+
240
289
// An Offering describes where an InstanceType is available to be used, with the expectation that its properties
241
290
// may be tightly coupled (e.g. the availability of an instance type in some zone is scoped to a capacity type) and
242
291
// these properties are captured with labels in Requirements.
243
292
// Requirements are required to contain the keys v1.CapacityTypeLabelKey and corev1.LabelTopologyZone
244
293
type Offering struct {
294
+ OfferingAvailabilityResolver
295
+
245
296
Requirements scheduling.Requirements
246
297
Price float64
247
- // Available is added so that Offerings can return all offerings that have ever existed for an instance type,
248
- // so we can get historical pricing data for calculating savings in consolidation
249
- Available bool
250
298
}
251
299
252
300
type Offerings []Offering
253
301
302
+ // Reserve attempts to make a reservation for each offering, returning true if it was successful for any.
303
+ func (ofs Offerings ) Reserve (id string ) bool {
304
+ success := false
305
+ for i := range ofs {
306
+ success = success || ofs [i ].Reserve (id )
307
+ }
308
+ return success
309
+ }
310
+
311
+ func (ofs Offerings ) Reservations (id string ) OfferingReservations {
312
+ return lo .FilterMap (ofs , func (o Offering , _ int ) (OfferingReservation , bool ) {
313
+ if reservation := o .GetReservation (id ); reservation != nil {
314
+ return reservation , true
315
+ }
316
+ return nil , false
317
+ })
318
+ }
319
+
254
320
// Available filters the available offerings from the returned offerings
255
321
func (ofs Offerings ) Available () Offerings {
256
322
return lo .Filter (ofs , func (o Offering , _ int ) bool {
257
- return o .Available
323
+ return o .Available ()
258
324
})
259
325
}
260
326
@@ -397,3 +463,42 @@ func NewCreateError(err error, message string) *CreateError {
397
463
ConditionMessage : message ,
398
464
}
399
465
}
466
+
467
+ type staticAvailabilityResolver struct {
468
+ requirements scheduling.Requirements
469
+ available bool
470
+ }
471
+
472
+ type noopReservation struct {
473
+ requirements scheduling.Requirements
474
+ }
475
+
476
+ func (r staticAvailabilityResolver ) Available () bool {
477
+ return r .available
478
+ }
479
+
480
+ func (r staticAvailabilityResolver ) Reserve (_ string ) bool {
481
+ return r .available
482
+ }
483
+
484
+ func (r staticAvailabilityResolver ) GetReservation (_ string ) OfferingReservation {
485
+ return noopReservation {
486
+ requirements : r .requirements ,
487
+ }
488
+ }
489
+
490
+ func (r noopReservation ) Commit () {}
491
+
492
+ func (r noopReservation ) Release () {}
493
+
494
+ func (r noopReservation ) Matches (nc * v1.NodeClaim ) bool {
495
+ reqs := scheduling .NewLabelRequirements (nc .Labels )
496
+ return reqs .IsCompatible (r .requirements , scheduling .AllowUndefinedWellKnownLabels )
497
+ }
498
+
499
+ func NewStaticAvailabilityResolver (available bool , requirements scheduling.Requirements ) OfferingAvailabilityResolver {
500
+ return staticAvailabilityResolver {
501
+ available : available ,
502
+ requirements : requirements ,
503
+ }
504
+ }
0 commit comments