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
2 changes: 1 addition & 1 deletion api/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const (
AcceleratorTypeLabel = "nebula.inftyai.com/accelerator-type"

// CapacityTypeAnnotation carries the optimizer-chosen purchase tier
// (Spot/OnDemand/Reserved). It is the one provisioning input that cannot be
// (Spot/OnDemand). It is the one provisioning input that cannot be
// read off the Pod's own spec, so the placement controller writes it here
// when it ungates the Pod. The virtual kubelet — which provisions solely from
// the Pod — reads it back on CreatePod. Empty means "let the provider use its
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/nodeclaim_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type NodeClaimSpec struct {
Provider string `json:"provider"`

// CapacityType is the purchase tier the placement optimizer selected
// (Spot/OnDemand/Reserved). It is stored durably here because it is the one
// (Spot/OnDemand). It is stored durably here because it is the one
// provisioning input that cannot be read off the Pod, and Provision needs it
// to re-issue the request after a controller restart. Immutable, like
// Provider. Empty means "let the provider use its default" (e.g. Modal is
Expand Down
10 changes: 3 additions & 7 deletions api/v1alpha1/nodepool_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ type NodePoolSpec struct {
// CapacityTypes is the OUTER axis: the purchase models to try, in fallback
// order. e.g. [Spot, OnDemand] means "use spot on any provider first; only
// when spot is exhausted everywhere, drop to on-demand". A single-element
// list pins the pool to that type. This replaces a spot on/off flag and
// extends to Reserved without new fields.
// list pins the pool to that type. This replaces a spot on/off flag.
// +kubebuilder:validation:MinItems=1
// +kubebuilder:default={Reserved,OnDemand,Spot}
// +kubebuilder:default={OnDemand,Spot}
CapacityTypes []CapacityType `json:"capacityTypes,omitempty"`

// Strategy is the INNER axis: how to rank providers within the active
Expand Down Expand Up @@ -116,17 +115,14 @@ const (

// CapacityType is the purchase model (the outer axis). Each provider maps it to
// its own concept — e.g. RunPod Spot -> interruptible/podRentInterruptable.
// +kubebuilder:validation:Enum=Spot;OnDemand;Reserved
// +kubebuilder:validation:Enum=Spot;OnDemand
type CapacityType string

const (
// CapacitySpot is interruptible/preemptible capacity (cheapest, reclaimable).
CapacitySpot CapacityType = "Spot"
// CapacityOnDemand is standard pay-as-you-go capacity.
CapacityOnDemand CapacityType = "OnDemand"
// CapacityReserved is pre-committed/reserved capacity (not all providers
// support it; reserved for future use).
CapacityReserved CapacityType = "Reserved"
)

// FailoverPolicy tunes capacity-error failover. Failover is always on — backing
Expand Down
3 changes: 1 addition & 2 deletions config/crd/bases/nebula.inftyai.com_nodeclaims.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,14 @@ spec:
capacityType:
description: |-
CapacityType is the purchase tier the placement optimizer selected
(Spot/OnDemand/Reserved). It is stored durably here because it is the one
(Spot/OnDemand). It is stored durably here because it is the one
provisioning input that cannot be read off the Pod, and Provision needs it
to re-issue the request after a controller restart. Immutable, like
Provider. Empty means "let the provider use its default" (e.g. Modal is
OnDemand-only and ignores it).
enum:
- Spot
- OnDemand
- Reserved
type: string
podRef:
description: |-
Expand Down
5 changes: 1 addition & 4 deletions config/crd/bases/nebula.inftyai.com_nodepools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,20 @@ spec:
properties:
capacityTypes:
default:
- Reserved
- OnDemand
- Spot
description: |-
CapacityTypes is the OUTER axis: the purchase models to try, in fallback
order. e.g. [Spot, OnDemand] means "use spot on any provider first; only
when spot is exhausted everywhere, drop to on-demand". A single-element
list pins the pool to that type. This replaces a spot on/off flag and
extends to Reserved without new fields.
list pins the pool to that type. This replaces a spot on/off flag.
items:
description: |-
CapacityType is the purchase model (the outer axis). Each provider maps it to
its own concept — e.g. RunPod Spot -> interruptible/podRentInterruptable.
enum:
- Spot
- OnDemand
- Reserved
type: string
minItems: 1
type: array
Expand Down
4 changes: 2 additions & 2 deletions config/samples/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-workload-sample-a100
name: gpu-workload-sample
namespace: default
labels:
app.kubernetes.io/managed-by: nebula
Expand All @@ -38,7 +38,7 @@ spec:
app: gpu-workload-sample
nebula.inftyai.com/enabled: "true"
nebula.inftyai.com/nodepool: sample
nebula.inftyai.com/accelerator-type: a100-40gb
nebula.inftyai.com/accelerator-type: l4
spec:
# Do NOT set nodeName or a provider nodeSelector yourself — the placement
# controller fills the nodeSelector in when it ungates the Pod. Setting
Expand Down
34 changes: 34 additions & 0 deletions internal/controller/nodepool_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,38 @@ var _ = Describe("NodePool spec validation (CEL)", func() {
Expect(k8sClient.Create(ctx, pool)).To(Succeed())
Expect(k8sClient.Delete(ctx, pool)).To(Succeed())
})

It("rejects a capacityType outside the Spot;OnDemand enum", func() {
// "Reserved" was a formerly-declared tier that never worked (the AWS adapter
// aliased it to a plain OnDemand launch) and has been removed from the enum.
// The CRD's x-kubernetes enum must now reject it so a stale spec cannot be
// admitted and silently degrade to OnDemand.
pool := &nebulav1alpha1.NodePool{
ObjectMeta: metav1.ObjectMeta{Name: "bad-capacity-type"},
Spec: nebulav1alpha1.NodePoolSpec{
Providers: []nebulav1alpha1.ProviderSpec{{Name: "modal"}},
CapacityTypes: []nebulav1alpha1.CapacityType{"Reserved"},
Strategy: nebulav1alpha1.StrategyOrdered,
},
}
err := k8sClient.Create(ctx, pool)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Unsupported value: \"Reserved\""))
})

It("admits the Spot and OnDemand capacity tiers", func() {
pool := &nebulav1alpha1.NodePool{
ObjectMeta: metav1.ObjectMeta{Name: "ok-capacity-types"},
Spec: nebulav1alpha1.NodePoolSpec{
Providers: []nebulav1alpha1.ProviderSpec{{Name: "modal"}},
CapacityTypes: []nebulav1alpha1.CapacityType{
nebulav1alpha1.CapacityOnDemand,
nebulav1alpha1.CapacitySpot,
},
Strategy: nebulav1alpha1.StrategyOrdered,
},
}
Expect(k8sClient.Create(ctx, pool)).To(Succeed())
Expect(k8sClient.Delete(ctx, pool)).To(Succeed())
})
})
2 changes: 1 addition & 1 deletion pkg/provider/catalog/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (c *Catalog) Offerings(providerName string) []provider.Offering {
const (
colAccelerator = "accelerator_type" // required: canonical Nebula accelerator type
colAcceleratorID = "accelerator_id" // optional: provider's own accelerator id (e.g. AWS instance type)
colCapacityType = "capacity_type" // required: Spot | OnDemand | Reserved
colCapacityType = "capacity_type" // required: Spot | OnDemand
colPrice = "price_per_hour" // required: approximate USD/GPU-hour
colAvailable = "available" // required: whether the provider offers it
colGPUCount = "gpu_count" // optional: accelerators the id provides (AWS: baked into instance type)
Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/catalog/data/aws.csv
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# accelerator_type canonical Nebula accelerator type
# accelerator_id EC2 instance type that serves {accelerator_type, gpu_count}
# gpu_count accelerators the instance type provides (the lookup key)
# capacity_type Spot | OnDemand | Reserved
# capacity_type Spot | OnDemand
# price_per_hour approximate USD/instance-hour (seed; live probe may override)
# available seed availability (the live probe is authoritative per region)
# region BLANK — stamped per configured region by the live probe
Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/catalog/data/modal.csv
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# parameter (off the Pod's nvidia.com/gpu resource), so it is
# not a catalog lookup dimension. AWS, where the count is baked
# into the instance type, fills it in.
# capacity_type Spot | OnDemand | Reserved
# capacity_type Spot | OnDemand
# price_per_hour approximate on-demand USD/GPU-hour
# available whether Modal currently offers it (a live probe may override)
# region BLANK — Modal is region-simple
Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ type ProvisionRequest struct {
// ClaimName is the NodeClaim name; providers without native tags encode it
// into the instance name so List/Terminate can find the instance later.
ClaimName string
// CapacityType is the tier the optimizer selected (Spot/OnDemand/Reserved).
// CapacityType is the tier the optimizer selected (Spot/OnDemand).
// This is the one workload-independent decision that cannot be expressed on
// the Pod, so it must be passed explicitly.
CapacityType nebulav1alpha1.CapacityType
Expand Down
Loading