Skip to content

Commit 5b443bf

Browse files
authored
Merge pull request #6619 from dperny/swarm-memory-swap
Add memory swap to swarm
2 parents eaa6114 + 71828f2 commit 5b443bf

File tree

15 files changed

+784
-16
lines changed

15 files changed

+784
-16
lines changed

cli/command/service/opts.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,17 @@ type resourceOptions struct {
235235
resCPU opts.NanoCPUs
236236
resMemBytes opts.MemBytes
237237
resGenericResources []string
238+
swapBytes opts.MemBytes
239+
memSwappiness int64
238240
}
239241

240-
func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements, error) {
242+
func (r *resourceOptions) ToResourceRequirements(flags *pflag.FlagSet) (*swarm.ResourceRequirements, error) {
241243
generic, err := ParseGenericResources(r.resGenericResources)
242244
if err != nil {
243245
return nil, err
244246
}
245247

246-
return &swarm.ResourceRequirements{
248+
resreq := &swarm.ResourceRequirements{
247249
Limits: &swarm.Limit{
248250
NanoCPUs: r.limitCPU.Value(),
249251
MemoryBytes: r.limitMemBytes.Value(),
@@ -254,7 +256,20 @@ func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements,
254256
MemoryBytes: r.resMemBytes.Value(),
255257
GenericResources: generic,
256258
},
257-
}, nil
259+
}
260+
261+
// SwapBytes and MemorySwappiness are *int64 (pointers), so we need to have
262+
// a variable we can take a pointer to. Additionally, we need to ensure
263+
// that these values are only set if they are set as options.
264+
if flags.Changed(flagSwapBytes) {
265+
swapBytes := r.swapBytes.Value()
266+
resreq.SwapBytes = &swapBytes
267+
}
268+
if flags.Changed(flagMemSwappiness) {
269+
resreq.MemorySwappiness = &r.memSwappiness
270+
}
271+
272+
return resreq, nil
258273
}
259274

260275
type restartPolicyOptions struct {
@@ -734,7 +749,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
734749
return networks[i].Target < networks[j].Target
735750
})
736751

737-
resources, err := options.resources.ToResourceRequirements()
752+
resources, err := options.resources.ToResourceRequirements(flags)
738753
if err != nil {
739754
return service, err
740755
}
@@ -889,6 +904,10 @@ func addServiceFlags(flags *pflag.FlagSet, options *serviceOptions, defaultFlagV
889904
flags.Var(&options.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
890905
flags.Int64Var(&options.resources.limitPids, flagLimitPids, 0, "Limit maximum number of processes (default 0 = unlimited)")
891906
flags.SetAnnotation(flagLimitPids, "version", []string{"1.41"})
907+
flags.Var(&options.resources.swapBytes, flagSwapBytes, "Swap Bytes (-1 for unlimited)")
908+
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
909+
flags.Int64Var(&options.resources.memSwappiness, flagMemSwappiness, -1, "Tune memory swappiness (0-100), -1 to reset to default")
910+
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
892911

893912
flags.Var(&options.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)"))
894913
flags.Var(&options.replicas, flagReplicas, "Number of tasks")
@@ -1073,6 +1092,8 @@ const (
10731092
flagUlimitAdd = "ulimit-add"
10741093
flagUlimitRemove = "ulimit-rm"
10751094
flagOomScoreAdj = "oom-score-adj"
1095+
flagSwapBytes = "memory-swap"
1096+
flagMemSwappiness = "memory-swappiness"
10761097
)
10771098

10781099
func toNetipAddrSlice(ips []string) []netip.Addr {

cli/command/service/opts_test.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,10 @@ func TestResourceOptionsToResourceRequirements(t *testing.T) {
163163
},
164164
}
165165

166+
flags := newCreateCommand(nil).Flags()
167+
166168
for _, opt := range incorrectOptions {
167-
_, err := opt.ToResourceRequirements()
169+
_, err := opt.ToResourceRequirements(flags)
168170
assert.Check(t, is.ErrorContains(err, ""))
169171
}
170172

@@ -178,12 +180,41 @@ func TestResourceOptionsToResourceRequirements(t *testing.T) {
178180
}
179181

180182
for _, opt := range correctOptions {
181-
r, err := opt.ToResourceRequirements()
183+
r, err := opt.ToResourceRequirements(flags)
182184
assert.NilError(t, err)
183185
assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources)))
184186
}
185187
}
186188

189+
func TestResourceOptionsToResourceRequirementsSwap(t *testing.T) {
190+
// first, check that no flag set means no field set in the return
191+
flags := newCreateCommand(nil).Flags()
192+
193+
// These should be the default values of the field.
194+
swapOptions := resourceOptions{
195+
swapBytes: 0,
196+
memSwappiness: -1,
197+
}
198+
199+
r, err := swapOptions.ToResourceRequirements(flags)
200+
assert.NilError(t, err)
201+
assert.Check(t, is.Nil(r.SwapBytes))
202+
assert.Check(t, is.Nil(r.MemorySwappiness))
203+
204+
// now set the flags and some values
205+
flags.Set(flagSwapBytes, "86000")
206+
flags.Set(flagMemSwappiness, "23")
207+
swapOptions.swapBytes = 86000
208+
swapOptions.memSwappiness = 23
209+
210+
r, err = swapOptions.ToResourceRequirements(flags)
211+
assert.NilError(t, err)
212+
assert.Check(t, r.SwapBytes != nil)
213+
assert.Check(t, is.Equal(*(r.SwapBytes), int64(86000)))
214+
assert.Check(t, r.MemorySwappiness != nil)
215+
assert.Check(t, is.Equal(*(r.MemorySwappiness), int64(23)))
216+
}
217+
187218
func TestToServiceNetwork(t *testing.T) {
188219
nws := []network.Inspect{
189220
{

cli/compose/convert/service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,10 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
560560
GenericResources: generic,
561561
}
562562
}
563+
// These fields are themselves pointers -- we can simply assign, no need to
564+
// nil-check them. Nil is nil.
565+
resources.SwapBytes = source.MemswapLimit
566+
resources.MemorySwappiness = source.MemSwappiness
563567
return resources, nil
564568
}
565569

cli/compose/convert/service_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ func TestConvertExtraHosts(t *testing.T) {
8181
}
8282

8383
func TestConvertResourcesFull(t *testing.T) {
84+
// create some variables so we can get pointers
85+
memswap := int64(72090)
86+
swappiness := int64(27)
8487
source := composetypes.Resources{
8588
Limits: &composetypes.ResourceLimit{
8689
NanoCPUs: "0.003",
@@ -90,6 +93,8 @@ func TestConvertResourcesFull(t *testing.T) {
9093
NanoCPUs: "0.002",
9194
MemoryBytes: composetypes.UnitBytes(200000000),
9295
},
96+
MemswapLimit: &memswap,
97+
MemSwappiness: &swappiness,
9398
}
9499
resources, err := convertResources(source)
95100
assert.NilError(t, err)
@@ -103,6 +108,8 @@ func TestConvertResourcesFull(t *testing.T) {
103108
NanoCPUs: 2000000,
104109
MemoryBytes: 200000000,
105110
},
111+
SwapBytes: &memswap,
112+
MemorySwappiness: &swappiness,
106113
}
107114
assert.Check(t, is.DeepEqual(expected, resources))
108115
}

cli/compose/loader/full-example.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "3.13"
1+
version: "3.14"
22

33
services:
44
foo:
@@ -79,6 +79,8 @@ services:
7979
- discrete_resource_spec:
8080
kind: 'ssd'
8181
value: 1
82+
memswap_limit: 86000
83+
mem_swappiness: 27
8284
restart_policy:
8385
condition: on-failure
8486
delay: 5s

cli/compose/loader/full-struct_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
func fullExampleConfig(workingDir, homeDir string) *types.Config {
1313
return &types.Config{
14-
Version: "3.13",
14+
Version: "3.14",
1515
Services: services(workingDir, homeDir),
1616
Networks: networks(),
1717
Volumes: volumes(),
@@ -108,6 +108,8 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
108108
},
109109
},
110110
},
111+
MemswapLimit: int64Ptr(86000),
112+
MemSwappiness: int64Ptr(27),
111113
},
112114
RestartPolicy: &types.RestartPolicy{
113115
Condition: "on-failure",

cli/compose/loader/loader_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func strPtr(val string) *string {
184184
}
185185

186186
var sampleConfig = types.Config{
187-
Version: "3.13",
187+
Version: "3.14",
188188
Services: []types.ServiceConfig{
189189
{
190190
Name: "foo",
@@ -970,6 +970,10 @@ func uint32Ptr(value uint32) *uint32 {
970970
return &value
971971
}
972972

973+
func int64Ptr(value int64) *int64 {
974+
return &value
975+
}
976+
973977
func TestFullExample(t *testing.T) {
974978
skip.If(t, runtime.GOOS == "windows", "FIXME: substitutes platform-specific HOME-dirs and requires platform-specific golden files; see https://github.com/docker/cli/pull/4610")
975979

cli/compose/loader/testdata/full-example.json.golden

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,9 @@
181181
}
182182
}
183183
]
184-
}
184+
},
185+
"memswap_limit": 86000,
186+
"mem_swappiness": 27
185187
},
186188
"restart_policy": {
187189
"condition": "on-failure",
@@ -513,7 +515,7 @@
513515
"working_dir": "/code"
514516
}
515517
},
516-
"version": "3.13",
518+
"version": "3.14",
517519
"volumes": {
518520
"another-volume": {
519521
"name": "user_specified_name",

cli/compose/loader/testdata/full-example.yaml.golden

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "3.13"
1+
version: "3.14"
22
services:
33
foo:
44
build:
@@ -73,6 +73,8 @@ services:
7373
- discrete_resource_spec:
7474
kind: ssd
7575
value: 1
76+
memswap_limit: 86000
77+
mem_swappiness: 27
7678
restart_policy:
7779
condition: on-failure
7880
delay: 5s

0 commit comments

Comments
 (0)