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
7 changes: 4 additions & 3 deletions config/fleet-contract.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ schema_version: 2
# a runner class withdrawn, a tenancy boundary narrowed, an admission rule
# changed. Adding a class or a tenant does not bump it -- that only widens what
# is on offer.
contract_version: 4
contract_version: 5

execution:
worker_kind: incus-container
Expand Down Expand Up @@ -77,8 +77,8 @@ observability:
guarantees:
- subject: runner-classes
promise: >-
A published runner class keeps its label and its capability shape,
including its bounded CPU tuning envelope. A class
A published runner class keeps its label, capability shape and minimum
memory/disk resources, including its bounded CPU tuning envelope. A class
is withdrawn only by a contract_version bump, and never silently: a label
that stops being served would leave a consumer's jobs queued forever
rather than failing.
Expand Down Expand Up @@ -114,6 +114,7 @@ guarantees:
not_contractual:
- Warm-pool depth, and whether a given job starts warm or cold.
- Per-host capacity and which host serves a given job.
- Per-deployment memory and disk ceilings above the published class minima.
- Image contents beyond the baked toolchains the class declares.
- Cache hit rate, and cache availability -- a cache outage degrades a build to
uncached rather than failing it.
Expand Down
2 changes: 1 addition & 1 deletion internal/fleetcontract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func ValidateConfig(contract Contract, platform platformconfig.Config) error {
pool.Capabilities.NetworkPolicy != class.NetworkPolicy || pool.Capabilities.CacheWriteScope != class.CacheWriteScope ||
pool.Capabilities.Docker != class.Docker || pool.Capabilities.Browser != class.Browser || pool.Resources.VCPU < class.Resources.VCPUMin ||
pool.Resources.VCPU > class.Resources.VCPUMax ||
pool.Resources.MemoryMiB != class.Resources.MemoryMiB || pool.Resources.DiskGiB != class.Resources.DiskGiB {
pool.Resources.MemoryMiB < class.Resources.MemoryMiB || pool.Resources.DiskGiB < class.Resources.DiskGiB {
// Warm depth is deliberately not compared. The contract's own
// not_contractual list says "Warm-pool depth, and whether a given
// job starts warm or cold", so a consumer may not build on it and
Expand Down
20 changes: 18 additions & 2 deletions internal/fleetcontract/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestPublicExampleContractBuildsWithoutEstateAccess(t *testing.T) {
len(contract.Tenants) == 0 || len(contract.Merge.RequiredContexts) != 1 || contract.Merge.RequiredContexts[0] != "Gate" {
t.Fatalf("public contract = %#v", contract)
}
if contract.SchemaVersion != 2 || contract.ContractVersion != 4 || contract.Execution.WorkerKind != "incus-container" ||
if contract.SchemaVersion != 2 || contract.ContractVersion != 5 || contract.Execution.WorkerKind != "incus-container" ||
!contract.Execution.Ephemeral || contract.Execution.JobsPerWorker != 1 || !contract.ResourceSemantics.HardMemoryExcludesEmergencySwap ||
contract.ResourceSemantics.EmergencySwapSchedulable || contract.ResourceSemantics.CPUMode != "weighted-overcommit" {
t.Fatalf("contract v2 semantics = %#v", contract)
Expand Down Expand Up @@ -99,7 +99,7 @@ func TestDeploymentOverlayCannotWeakenContract(t *testing.T) {
"vm worker": func(candidate *config.Config) { candidate.ControlPlane.WorkerKind = "incus-vm" },
"schedulable swap": func(candidate *config.Config) { candidate.Guardrails.EmergencySwapSchedulable = true },
"pressure disabled": func(candidate *config.Config) { candidate.Pressure.Required = false },
"resource drift": func(candidate *config.Config) { candidate.Pools[0].Resources.MemoryMiB++ },
"memory below minimum": func(candidate *config.Config) { candidate.Pools[0].Resources.MemoryMiB-- },
} {
t.Run(name, func(t *testing.T) {
candidate := platform
Expand All @@ -112,6 +112,22 @@ func TestDeploymentOverlayCannotWeakenContract(t *testing.T) {
}
}

func TestDeploymentOverlayMayRaiseResourceCeilings(t *testing.T) {
contract, err := Build(Sources{Root: "../.."}, "0123456789abcdef0123456789abcdef01234567")
if err != nil {
t.Fatal(err)
}
platform, err := config.Load(filepath.Join("..", "..", "config", "example-services.yaml"))
if err != nil {
t.Fatal(err)
}
platform.Pools[0].Resources.MemoryMiB += 2048
platform.Pools[0].Resources.DiskGiB += 10
if err := ValidateConfig(contract, platform); err != nil {
t.Fatalf("resource expansion above the published minimum was rejected: %v", err)
}
}

func TestDeploymentOverlayMayTuneCPUInsidePublishedEnvelope(t *testing.T) {
contract, err := Build(Sources{Root: "../.."}, "0123456789abcdef0123456789abcdef01234567")
if err != nil {
Expand Down