Skip to content

Commit 2fe4965

Browse files
runsc: add update command
This change enables updating resource constraints of running containers. It is the runsc equivalent of runc update.
1 parent d06b27e commit 2fe4965

File tree

12 files changed

+663
-35
lines changed

12 files changed

+663
-35
lines changed

pkg/test/dockerutil/container.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,14 @@ func (c *Container) CleanUp(ctx context.Context) {
712712
c.mounts = nil
713713
}
714714

715+
// Update is analogous to 'docker update'.
716+
func (c *Container) Update(ctx context.Context, updateConfig container.UpdateConfig) error {
717+
if _, err := c.client.ContainerUpdate(ctx, c.id, updateConfig); err != nil {
718+
return fmt.Errorf("updating container %s: %v", c.id, err)
719+
}
720+
return nil
721+
}
722+
715723
// ContainerPool represents a pool of reusable containers.
716724
// Callers may request a container from the pool, and must release it back
717725
// when they are done with it.

runsc/cgroup/cgroup.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ func loadPathsHelper(cgroup, mountinfo io.Reader, unified bool) (map[string]stri
316316
// Cgroup represents a cgroup configuration.
317317
type Cgroup interface {
318318
Install(res *specs.LinuxResources) error
319+
Update(res *specs.LinuxResources) error
319320
Uninstall() error
320321
Join() (func(), error)
321322
CPUQuota() (float64, error)
@@ -542,6 +543,34 @@ func (c *cgroupV1) Install(res *specs.LinuxResources) error {
542543
return nil
543544
}
544545

546+
// Update updates the cgroup resources.
547+
func (c *cgroupV1) Update(res *specs.LinuxResources) error {
548+
log.Debugf("Updating cgroup resources for %q", c.Name)
549+
for key, ctrlr := range controllers {
550+
if !c.Own[key] {
551+
// cgroup is managed by caller, don't touch it.
552+
continue
553+
}
554+
path := c.MakePath(key)
555+
if _, err := os.Stat(path); err != nil {
556+
if os.IsNotExist(err) && ctrlr.optional() {
557+
if err := ctrlr.skip(res); err != nil {
558+
return err
559+
}
560+
log.Infof("Skipping cgroup %q, path doesn't exist", key)
561+
continue
562+
}
563+
return fmt.Errorf("failed to stat cgroup %q: %w", key, err)
564+
}
565+
566+
if err := ctrlr.set(res, path); err != nil {
567+
return fmt.Errorf("failed to set %q cgroup: %w", key, err)
568+
}
569+
}
570+
571+
return nil
572+
}
573+
545574
// createController creates the controller directory, checking that the
546575
// controller is enabled in the system. It returns a boolean indicating whether
547576
// the controller should be skipped (e.g. controller is disabled). In case it

runsc/cgroup/cgroup_v2.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ func (c *cgroupV2) Install(res *specs.LinuxResources) error {
146146
for _, knownController := range c.Controllers {
147147
if controllerName == knownController {
148148
found = true
149+
break
149150
}
150151
}
151152

@@ -170,6 +171,38 @@ func (c *cgroupV2) Install(res *specs.LinuxResources) error {
170171
return nil
171172
}
172173

174+
// Update updates the cgroup resources.
175+
func (c *cgroupV2) Update(res *specs.LinuxResources) error {
176+
path := c.MakePath("")
177+
log.Debugf("Updating cgroup resources for %q", path)
178+
for controllerName, ctrlr := range controllers2 {
179+
// First check if our controller is found in the system.
180+
found := false
181+
for _, knownController := range c.Controllers {
182+
if controllerName == knownController {
183+
found = true
184+
break
185+
}
186+
}
187+
188+
// In case we don't have the controller.
189+
if found {
190+
if err := ctrlr.set(res, path); err != nil {
191+
return err
192+
}
193+
continue
194+
}
195+
if ctrlr.optional() {
196+
if err := ctrlr.skip(res); err != nil {
197+
return err
198+
}
199+
} else {
200+
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, path)
201+
}
202+
}
203+
return nil
204+
}
205+
173206
// Uninstall removes the settings done in Install(). If cgroup path already
174207
// existed when Install() was called, Uninstall is a noop.
175208
func (c *cgroupV2) Uninstall() error {
@@ -540,7 +573,7 @@ func (*memory2) set(spec *specs.LinuxResources, path string) error {
540573

541574
swap, err := convertMemorySwapToCgroupV2Value(*spec.Memory.Swap, *spec.Memory.Limit)
542575
if err != nil {
543-
return nil
576+
return err
544577
}
545578
swapStr := numToStr(swap)
546579
// memory and memorySwap set to the same value -- disable swap

runsc/cgroup/cgroup_v2_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,88 @@ func TestParseCPUQuota(t *testing.T) {
432432
}
433433
}
434434
}
435+
436+
func TestUpdate(t *testing.T) {
437+
for _, tc := range []struct {
438+
name string
439+
initialCPUMax string
440+
initialMemMax string
441+
updatedResources *specs.LinuxResources
442+
wantCPUQuota float64
443+
wantMemory uint64
444+
}{
445+
{
446+
name: "update cpu and memory",
447+
initialCPUMax: "100 100",
448+
initialMemMax: "1024",
449+
updatedResources: &specs.LinuxResources{
450+
CPU: &specs.LinuxCPU{
451+
Quota: int64Ptr(100),
452+
Period: uint64Ptr(50),
453+
},
454+
Memory: &specs.LinuxMemory{
455+
Limit: int64Ptr(2048),
456+
},
457+
},
458+
wantCPUQuota: 2.0,
459+
wantMemory: 2048,
460+
},
461+
{
462+
name: "update cpu only",
463+
initialCPUMax: "100 100",
464+
initialMemMax: "1024",
465+
updatedResources: &specs.LinuxResources{
466+
CPU: &specs.LinuxCPU{
467+
Quota: int64Ptr(150),
468+
Period: uint64Ptr(50),
469+
},
470+
},
471+
wantCPUQuota: 3.0,
472+
wantMemory: 1024,
473+
},
474+
} {
475+
t.Run(tc.name, func(t *testing.T) {
476+
dir, err := os.MkdirTemp(testutil.TmpDir(), "cgroup")
477+
if err != nil {
478+
t.Fatalf("error creating temporary directory: %v", err)
479+
}
480+
defer os.RemoveAll(dir)
481+
482+
cg := &cgroupV2{
483+
Mountpoint: dir,
484+
Path: "user.slice",
485+
Controllers: mandatoryControllers,
486+
}
487+
if err := os.MkdirAll(filepath.Join(cg.Mountpoint, cg.Path), 0o777); err != nil {
488+
t.Fatalf("os.MkdirAll(): %v", err)
489+
}
490+
491+
if err := os.WriteFile(filepath.Join(cg.Mountpoint, cg.Path, "cpu.max"), []byte(tc.initialCPUMax), 0o777); err != nil {
492+
t.Fatalf("os.WriteFile(): %v", err)
493+
}
494+
if err := os.WriteFile(filepath.Join(cg.Mountpoint, cg.Path, "memory.max"), []byte(tc.initialMemMax), 0o777); err != nil {
495+
t.Fatalf("os.WriteFile(): %v", err)
496+
}
497+
498+
if err := cg.Update(tc.updatedResources); err != nil {
499+
t.Fatalf("Update(): %v", err)
500+
}
501+
502+
cpuQuota, err := cg.CPUQuota()
503+
if err != nil {
504+
t.Fatalf("CPUQuota(): %v", err)
505+
}
506+
if cpuQuota != tc.wantCPUQuota {
507+
t.Fatalf("After Update(), CPUQuota() = %f, want %f", cpuQuota, tc.wantCPUQuota)
508+
}
509+
510+
mem, err := cg.MemoryLimit()
511+
if err != nil {
512+
t.Fatalf("MemoryLimit(): %v", err)
513+
}
514+
if mem != tc.wantMemory {
515+
t.Fatalf("After Update(), MemoryLimit() = %d, want %d", mem, tc.wantMemory)
516+
}
517+
})
518+
}
519+
}

runsc/cgroup/systemd.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ func (c *cgroupSystemd) Install(res *specs.LinuxResources) error {
117117
for _, knownController := range c.Controllers {
118118
if controllerName == knownController {
119119
found = true
120+
break
120121
}
121122
}
122123
if found {
@@ -138,6 +139,48 @@ func (c *cgroupSystemd) Install(res *specs.LinuxResources) error {
138139
return nil
139140
}
140141

142+
// Update updates the cgroup resources of an existing systemd unit.
143+
func (c *cgroupSystemd) Update(res *specs.LinuxResources) error {
144+
log.Debugf("Updating systemd cgroup resource controller under %v", c.Parent)
145+
for controllerName, ctrlr := range controllers2 {
146+
// First check if our controller is found in the system.
147+
found := false
148+
for _, knownController := range c.Controllers {
149+
if controllerName == knownController {
150+
found = true
151+
break
152+
}
153+
}
154+
if found {
155+
props, err := ctrlr.generateProperties(res)
156+
if err != nil {
157+
return err
158+
}
159+
c.properties = append(c.properties, props...)
160+
continue
161+
}
162+
if ctrlr.optional() {
163+
if err := ctrlr.skip(res); err != nil {
164+
return err
165+
}
166+
} else {
167+
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, c.Path)
168+
}
169+
}
170+
171+
ctx := context.Background()
172+
conn, err := systemdDbus.NewWithContext(ctx)
173+
if err != nil {
174+
return err
175+
}
176+
c.dbusConn = conn
177+
// Our systemd units are transient, hence runtime=true.
178+
if err := c.dbusConn.SetUnitPropertiesContext(ctx, c.unitName(), true /* runtime */, c.properties...); err != nil {
179+
return fmt.Errorf("error setting systemd unit properties: %v", err)
180+
}
181+
return nil
182+
}
183+
141184
func (c *cgroupSystemd) unitName() string {
142185
return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name)
143186
}

runsc/cgroup/systemd_test.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,14 @@ func TestExpandSlice(t *testing.T) {
8585
}
8686

8787
func TestInstall(t *testing.T) {
88+
const dialErr = "dial unix /var/run/dbus/system_bus_socket: connect: no such file or directory"
8889
for _, tc := range []struct {
89-
name string
90-
res *specs.LinuxResources
91-
wantProps []systemdDbus.Property
92-
err error
90+
name string
91+
res *specs.LinuxResources
92+
wantProps []systemdDbus.Property
93+
updatedRes *specs.LinuxResources
94+
wantUpdatedProps []systemdDbus.Property
95+
err error
9396
}{
9497
{
9598
name: "defaults",
@@ -160,6 +163,29 @@ func TestInstall(t *testing.T) {
160163
{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 4})},
161164
{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 5})},
162165
},
166+
updatedRes: &specs.LinuxResources{
167+
CPU: &specs.LinuxCPU{
168+
Shares: uint64Ptr(1),
169+
Period: uint64Ptr(10000),
170+
Quota: int64Ptr(300000),
171+
Cpus: "2",
172+
Mems: "3",
173+
},
174+
},
175+
wantUpdatedProps: []systemdDbus.Property{
176+
// initial properties
177+
{"CPUWeight", dbus.MakeVariant(convertCPUSharesToCgroupV2Value(1))},
178+
{"CPUQuotaPeriodUSec", dbus.MakeVariant(uint64(20000))},
179+
{"CPUQuotaPerSecUSec", dbus.MakeVariant(uint64(15000000))},
180+
{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 4})},
181+
{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 5})},
182+
// updated properties
183+
{"CPUWeight", dbus.MakeVariant(convertCPUSharesToCgroupV2Value(1))},
184+
{"CPUQuotaPeriodUSec", dbus.MakeVariant(uint64(10000))},
185+
{"CPUQuotaPerSecUSec", dbus.MakeVariant(uint64(30000000))},
186+
{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 2})},
187+
{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 3})},
188+
},
163189
},
164190
{
165191
name: "cpuset",
@@ -213,9 +239,10 @@ func TestInstall(t *testing.T) {
213239
t.Run(tc.name, func(t *testing.T) {
214240
cg := cgroupSystemd{Name: "123", Parent: "parent.slice"}
215241
cg.Controllers = mandatoryControllers
216-
err := cg.Install(tc.res)
217-
if !errors.Is(err, tc.err) {
218-
t.Fatalf("Wrong error, got: %s, want: %s", err, tc.err)
242+
if err := cg.Install(tc.res); err != nil && err.Error() != dialErr {
243+
if !errors.Is(err, tc.err) {
244+
t.Fatalf("Wrong error, got: %s, want: %s", err, tc.err)
245+
}
219246
}
220247
cmper := cmp.Comparer(func(a dbus.Variant, b dbus.Variant) bool {
221248
return a.String() == b.String()
@@ -227,6 +254,18 @@ func TestInstall(t *testing.T) {
227254
if diff := cmp.Diff(filteredProps, tc.wantProps, cmper, sorter); diff != "" {
228255
t.Errorf("cgroup properties list diff %s", diff)
229256
}
257+
258+
if tc.updatedRes != nil {
259+
if err := cg.Update(tc.updatedRes); err != nil && err.Error() != dialErr {
260+
if !errors.Is(err, tc.err) {
261+
t.Fatalf("Wrong error, got: %s, want: %s", err, tc.err)
262+
}
263+
}
264+
filteredProps = filterProperties(cg.properties, tc.wantUpdatedProps)
265+
if diff := cmp.Diff(filteredProps, tc.wantUpdatedProps, cmper, sorter); diff != "" {
266+
t.Errorf("cgroup properties list diff %s", diff)
267+
}
268+
}
230269
})
231270
}
232271
}

runsc/cli/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ func forEachCmd(cb func(cmd subcommands.Command, group string)) {
258258
cb(new(cmd.Spec), "")
259259
cb(new(cmd.Start), "")
260260
cb(new(cmd.State), "")
261+
cb(new(cmd.Update), "")
261262
cb(new(cmd.Wait), "")
262263

263264
// Helpers.

runsc/cmd/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ go_library(
7171
"symbolize.go",
7272
"syscalls.go",
7373
"umount_unsafe.go",
74+
"update.go",
7475
"usage.go",
7576
"wait.go",
7677
"write_control.go",

0 commit comments

Comments
 (0)