Skip to content

Commit 6e95fb0

Browse files
authored
support soft-only memory oversubscription (#488)
Implement driver-side support in the `podman` drivers for `resource.memory_max = -1`, which allows a reserve-only memory request for clusters with oversubscription enabled. This was already allowed by the server, but undocumented and unevenly supported by drivers. Ref: hashicorp/nomad#27354 Ref: https://hashicorp.atlassian.net/browse/NMD-911 Ref: hashicorp/web-unified-docs#1629
1 parent 2ea4c59 commit 6e95fb0

File tree

2 files changed

+70
-32
lines changed

2 files changed

+70
-32
lines changed

driver.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -680,11 +680,11 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
680680
return nil, nil, err
681681
}
682682

683-
hard, soft, err := memoryLimits(cfg.Resources.NomadResources.Memory, podmanTaskConfig.MemoryReservation)
683+
hard, reserved, err := memoryLimits(cfg.Resources.NomadResources.Memory, podmanTaskConfig.MemoryReservation)
684684
if err != nil {
685685
return nil, nil, err
686686
}
687-
createOpts.ContainerResourceConfig.ResourceLimits.Memory.Reservation = soft
687+
createOpts.ContainerResourceConfig.ResourceLimits.Memory.Reservation = reserved
688688
createOpts.ContainerResourceConfig.ResourceLimits.Memory.Limit = hard
689689
// set PidsLimit only if configured.
690690
if podmanTaskConfig.PidsLimit > 0 {
@@ -1034,31 +1034,59 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
10341034
return handle, driverNet, nil
10351035
}
10361036

1037+
const (
1038+
// memoryNoLimit is a sentinel value for memory_max that indicates the
1039+
// driver should not enforce a maximum memory limit
1040+
memoryNoLimit = -1
1041+
)
1042+
1043+
// memoryLimits computes the memory and memory_reservation values passed along to
1044+
// the podman host config. These fields represent hard and soft/reserved memory
1045+
// limits from podman's perspective, respectively.
1046+
//
1047+
// The resources.memory field in the jobspec is normally interpreted as a hard
1048+
// limit.
1049+
//
1050+
// If task.config.memory_reservation is set, it is treated as the reserve and
1051+
// resources.memory is the hard limit. This entirely bypasses the scheduler
1052+
// oversubscription setting.
1053+
//
1054+
// If oversubscription is enabled and resources.memory_max is set,
1055+
// resources.memory_max is treated as the hard limit and either resources.memory
1056+
// or task.config.memory_reservation (whichever is less) is the soft limit. If
1057+
// resources.memory_max = -1, there is no hard limit.
1058+
//
1059+
// Returns (memory (hard), memory_reservation (soft)) values in bytes.
10371060
func memoryLimits(r drivers.MemoryResources, reservation string) (hard, soft *int64, err error) {
1038-
memoryMax := r.MemoryMaxMB * 1024 * 1024
1061+
memoryMax := r.MemoryMaxMB
10391062
memory := r.MemoryMB * 1024 * 1024
1040-
10411063
var reserved *int64
1064+
10421065
if reservation != "" {
10431066
reservation, err := memoryInBytes(reservation)
10441067
if err != nil {
10451068
return nil, nil, err
10461069
}
10471070
reserved = &reservation
10481071
}
1049-
1072+
if memoryMax == memoryNoLimit {
1073+
if reserved != nil && *reserved < memory {
1074+
return nil, reserved, nil
1075+
}
1076+
return nil, &memory, nil
1077+
}
10501078
if memoryMax > 0 {
1079+
memoryMax = memoryMax * 1024 * 1024
10511080
if reserved != nil && *reserved < memory {
10521081
memory = *reserved
10531082
}
10541083
return &memoryMax, &memory, nil
10551084
}
1056-
10571085
if memory > 0 {
10581086
return &memory, reserved, nil
10591087
}
10601088

1061-
// We may never actually be here
1089+
// should be unreachable b/c a default should always be set by the server
10621090
return nil, reserved, nil
10631091
}
10641092

driver_test.go

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2522,77 +2522,87 @@ func Test_memoryLimits(t *testing.T) {
25222522
ci.Parallel(t)
25232523

25242524
cases := []struct {
2525-
name string
2526-
memResources drivers.MemoryResources
2527-
reservation string
2528-
expectedHard int64
2529-
expectedSoft int64
2525+
name string
2526+
memResources drivers.MemoryResources
2527+
reservation string
2528+
expectedHard int64
2529+
expectedReserve int64
25302530
}{
25312531
{
25322532
name: "plain",
25332533
memResources: drivers.MemoryResources{
25342534
MemoryMB: 20,
25352535
},
2536-
expectedHard: 20 * 1024 * 1024,
2537-
expectedSoft: 0,
2536+
expectedHard: 20 * 1024 * 1024,
2537+
expectedReserve: 0,
25382538
},
25392539
{
25402540
name: "memory oversubscription",
25412541
memResources: drivers.MemoryResources{
25422542
MemoryMB: 20,
25432543
MemoryMaxMB: 30,
25442544
},
2545-
expectedHard: 30 * 1024 * 1024,
2546-
expectedSoft: 20 * 1024 * 1024,
2545+
expectedHard: 30 * 1024 * 1024,
2546+
expectedReserve: 20 * 1024 * 1024,
25472547
},
25482548
{
25492549
name: "plain but using memory reservations",
25502550
memResources: drivers.MemoryResources{
25512551
MemoryMB: 20,
25522552
},
2553-
reservation: "10m",
2554-
expectedHard: 20 * 1024 * 1024,
2555-
expectedSoft: 10 * 1024 * 1024,
2553+
reservation: "10m",
2554+
expectedHard: 20 * 1024 * 1024,
2555+
expectedReserve: 10 * 1024 * 1024,
25562556
},
25572557
{
25582558
name: "oversubscription but with specifying memory reservation",
25592559
memResources: drivers.MemoryResources{
25602560
MemoryMB: 20,
25612561
MemoryMaxMB: 30,
25622562
},
2563-
reservation: "10m",
2564-
expectedHard: 30 * 1024 * 1024,
2565-
expectedSoft: 10 * 1024 * 1024,
2563+
reservation: "10m",
2564+
expectedHard: 30 * 1024 * 1024,
2565+
expectedReserve: 10 * 1024 * 1024,
25662566
},
25672567
{
25682568
name: "oversubscription but with specifying high memory reservation",
25692569
memResources: drivers.MemoryResources{
25702570
MemoryMB: 20,
25712571
MemoryMaxMB: 30,
25722572
},
2573-
reservation: "25m",
2574-
expectedHard: 30 * 1024 * 1024,
2575-
expectedSoft: 20 * 1024 * 1024,
2573+
reservation: "25m",
2574+
expectedHard: 30 * 1024 * 1024,
2575+
expectedReserve: 20 * 1024 * 1024,
2576+
},
2577+
{
2578+
name: "oversubscription without hard limit",
2579+
memResources: drivers.MemoryResources{
2580+
MemoryMB: 20,
2581+
MemoryMaxMB: -1,
2582+
},
2583+
reservation: "25m",
2584+
expectedHard: 0,
2585+
expectedReserve: 20 * 1024 * 1024,
25762586
},
25772587
}
25782588

25792589
for _, c := range cases {
25802590
t.Run(c.name, func(t *testing.T) {
2581-
hard, soft, err := memoryLimits(c.memResources, c.reservation)
2591+
hard, reserve, err := memoryLimits(c.memResources, c.reservation)
25822592
must.NoError(t, err)
25832593

25842594
if c.expectedHard > 0 {
2585-
must.NotNil(t, hard)
2595+
must.NotNil(t, hard, must.Sprintf("hard limit was nil"))
25862596
must.Eq(t, c.expectedHard, *hard)
25872597
} else {
2588-
must.Nil(t, hard)
2598+
must.Nil(t, hard, must.Sprintf("hard limit was not nil: %d", hard))
25892599
}
25902600

2591-
if c.expectedSoft > 0 {
2592-
must.NotNil(t, soft)
2593-
must.Eq(t, c.expectedSoft, *soft)
2601+
if c.expectedReserve > 0 {
2602+
must.NotNil(t, reserve, must.Sprint("reserve was nil"))
2603+
must.Eq(t, c.expectedReserve, *reserve)
25942604
} else {
2595-
must.Nil(t, soft)
2605+
must.Nil(t, reserve, must.Sprintf("reserve was not nil: %d", reserve))
25962606
}
25972607
})
25982608
}

0 commit comments

Comments
 (0)