Skip to content

Add runsc update command #9876

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions pkg/test/dockerutil/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,14 @@ func (c *Container) CleanUp(ctx context.Context) {
c.mounts = nil
}

// Update is analogous to 'docker update'.
func (c *Container) Update(ctx context.Context, updateConfig container.UpdateConfig) error {
if _, err := c.client.ContainerUpdate(ctx, c.id, updateConfig); err != nil {
return fmt.Errorf("updating container %s: %v", c.id, err)
}
return nil
}

// ContainerPool represents a pool of reusable containers.
// Callers may request a container from the pool, and must release it back
// when they are done with it.
Expand Down
29 changes: 29 additions & 0 deletions runsc/cgroup/cgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ func loadPathsHelper(cgroup, mountinfo io.Reader, unified bool) (map[string]stri
// Cgroup represents a cgroup configuration.
type Cgroup interface {
Install(res *specs.LinuxResources) error
Update(res *specs.LinuxResources) error
Uninstall() error
Join() (func(), error)
CPUQuota() (float64, error)
Expand Down Expand Up @@ -542,6 +543,34 @@ func (c *cgroupV1) Install(res *specs.LinuxResources) error {
return nil
}

// Update updates the cgroup resources.
func (c *cgroupV1) Update(res *specs.LinuxResources) error {
log.Debugf("Updating cgroup resources for %q", c.Name)
for key, ctrlr := range controllers {
if !c.Own[key] {
// cgroup is managed by caller, don't touch it.
continue
}
path := c.MakePath(key)
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) && ctrlr.optional() {
if err := ctrlr.skip(res); err != nil {
return err
}
log.Infof("Skipping cgroup %q, path doesn't exist", key)
continue
}
return fmt.Errorf("failed to stat cgroup %q: %w", key, err)
}

if err := ctrlr.set(res, path); err != nil {
return fmt.Errorf("failed to set %q cgroup: %w", key, err)
}
}

return nil
}

// createController creates the controller directory, checking that the
// controller is enabled in the system. It returns a boolean indicating whether
// the controller should be skipped (e.g. controller is disabled). In case it
Expand Down
56 changes: 33 additions & 23 deletions runsc/cgroup/cgroup_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,34 +139,44 @@ func (c *cgroupV2) Install(res *specs.LinuxResources) error {
return err
}
if created {
// If we created our final cgroup path then we can set the resources.
for controllerName, ctrlr := range controllers2 {
// First check if our controller is found in the system.
found := false
for _, knownController := range c.Controllers {
if controllerName == knownController {
found = true
}
if err := c.Update(res); err != nil {
return err
}
}

clean.Release()
return nil
}

// Update updates the cgroup resources.
func (c *cgroupV2) Update(res *specs.LinuxResources) error {
path := c.MakePath("")
log.Debugf("Updating cgroup resources for %q", path)
for controllerName, ctrlr := range controllers2 {
// First check if our controller is found in the system.
found := false
for _, knownController := range c.Controllers {
if controllerName == knownController {
found = true
break
}
}

// In case we don't have the controller.
if found {
if err := ctrlr.set(res, c.MakePath("")); err != nil {
return err
}
continue
// In case we don't have the controller.
if found {
if err := ctrlr.set(res, path); err != nil {
return err
}
if ctrlr.optional() {
if err := ctrlr.skip(res); err != nil {
return err
}
} else {
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, c.MakePath(""))
continue
}
if ctrlr.optional() {
if err := ctrlr.skip(res); err != nil {
return err
}
} else {
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, path)
}
}

clean.Release()
return nil
}

Expand Down Expand Up @@ -540,7 +550,7 @@ func (*memory2) set(spec *specs.LinuxResources, path string) error {

swap, err := convertMemorySwapToCgroupV2Value(*spec.Memory.Swap, *spec.Memory.Limit)
if err != nil {
return nil
return err
}
swapStr := numToStr(swap)
// memory and memorySwap set to the same value -- disable swap
Expand Down
85 changes: 85 additions & 0 deletions runsc/cgroup/cgroup_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,88 @@ func TestParseCPUQuota(t *testing.T) {
}
}
}

func TestUpdate(t *testing.T) {
for _, tc := range []struct {
name string
initialCPUMax string
initialMemMax string
updatedResources *specs.LinuxResources
wantCPUQuota float64
wantMemory uint64
}{
{
name: "update cpu and memory",
initialCPUMax: "100 100",
initialMemMax: "1024",
updatedResources: &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Quota: int64Ptr(100),
Period: uint64Ptr(50),
},
Memory: &specs.LinuxMemory{
Limit: int64Ptr(2048),
},
},
wantCPUQuota: 2.0,
wantMemory: 2048,
},
{
name: "update cpu only",
initialCPUMax: "100 100",
initialMemMax: "1024",
updatedResources: &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Quota: int64Ptr(150),
Period: uint64Ptr(50),
},
},
wantCPUQuota: 3.0,
wantMemory: 1024,
},
} {
t.Run(tc.name, func(t *testing.T) {
dir, err := os.MkdirTemp(testutil.TmpDir(), "cgroup")
if err != nil {
t.Fatalf("error creating temporary directory: %v", err)
}
defer os.RemoveAll(dir)

cg := &cgroupV2{
Mountpoint: dir,
Path: "user.slice",
Controllers: mandatoryControllers,
}
if err := os.MkdirAll(filepath.Join(cg.Mountpoint, cg.Path), 0o777); err != nil {
t.Fatalf("os.MkdirAll(): %v", err)
}

if err := os.WriteFile(filepath.Join(cg.Mountpoint, cg.Path, "cpu.max"), []byte(tc.initialCPUMax), 0o777); err != nil {
t.Fatalf("os.WriteFile(): %v", err)
}
if err := os.WriteFile(filepath.Join(cg.Mountpoint, cg.Path, "memory.max"), []byte(tc.initialMemMax), 0o777); err != nil {
t.Fatalf("os.WriteFile(): %v", err)
}

if err := cg.Update(tc.updatedResources); err != nil {
t.Fatalf("Update(): %v", err)
}

cpuQuota, err := cg.CPUQuota()
if err != nil {
t.Fatalf("CPUQuota(): %v", err)
}
if cpuQuota != tc.wantCPUQuota {
t.Fatalf("After Update(), CPUQuota() = %f, want %f", cpuQuota, tc.wantCPUQuota)
}

mem, err := cg.MemoryLimit()
if err != nil {
t.Fatalf("MemoryLimit(): %v", err)
}
if mem != tc.wantMemory {
t.Fatalf("After Update(), MemoryLimit() = %d, want %d", mem, tc.wantMemory)
}
})
}
}
71 changes: 48 additions & 23 deletions runsc/cgroup/systemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,29 +111,25 @@ func (c *cgroupSystemd) Install(res *specs.LinuxResources) error {
// For compatibility with runc.
c.addProp("DefaultDependencies", false)

for controllerName, ctrlr := range controllers2 {
// First check if our controller is found in the system.
found := false
for _, knownController := range c.Controllers {
if controllerName == knownController {
found = true
}
}
if found {
props, err := ctrlr.generateProperties(res)
if err != nil {
return err
}
c.properties = append(c.properties, props...)
continue
}
if ctrlr.optional() {
if err := ctrlr.skip(res); err != nil {
return err
}
} else {
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, c.Path)
}
return c.updateControllersProps(res)
}

// Update updates the cgroup resources of an existing systemd unit.
func (c *cgroupSystemd) Update(res *specs.LinuxResources) error {
log.Debugf("Updating systemd cgroup resource controller under %v", c.Parent)
if err := c.updateControllersProps(res); err != nil {
return err
}

ctx := context.Background()
conn, err := systemdDbus.NewWithContext(ctx)
if err != nil {
return err
}
c.dbusConn = conn
// Our systemd units are transient, hence runtime=true.
if err := c.dbusConn.SetUnitPropertiesContext(ctx, c.unitName(), true /* runtime */, c.properties...); err != nil {
return fmt.Errorf("error setting systemd unit properties: %v", err)
}
return nil
}
Expand Down Expand Up @@ -306,6 +302,35 @@ func newProp(name string, units any) systemdDbus.Property {
}
}

func (c *cgroupSystemd) updateControllersProps(res *specs.LinuxResources) error {
for controllerName, ctrlr := range controllers2 {
// First check if our controller is found in the system.
found := false
for _, knownController := range c.Controllers {
if controllerName == knownController {
found = true
break
}
}
if found {
props, err := ctrlr.generateProperties(res)
if err != nil {
return err
}
c.properties = append(c.properties, props...)
continue
}
if ctrlr.optional() {
if err := ctrlr.skip(res); err != nil {
return err
}
} else {
return fmt.Errorf("mandatory cgroup controller %q is missing for %q", controllerName, c.Path)
}
}
return nil
}

// CreateMockSystemdCgroup returns a mock Cgroup configured for systemd. This
// is useful for testing.
func CreateMockSystemdCgroup() Cgroup {
Expand Down
46 changes: 42 additions & 4 deletions runsc/cgroup/systemd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,14 @@ func TestExpandSlice(t *testing.T) {
}

func TestInstall(t *testing.T) {
const dialErr = "dial unix /var/run/dbus/system_bus_socket: connect: no such file or directory"
for _, tc := range []struct {
name string
res *specs.LinuxResources
wantProps []systemdDbus.Property
err error
name string
res *specs.LinuxResources
wantProps []systemdDbus.Property
updatedRes *specs.LinuxResources
wantUpdatedProps []systemdDbus.Property
err error
}{
{
name: "defaults",
Expand Down Expand Up @@ -160,6 +163,29 @@ func TestInstall(t *testing.T) {
{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 4})},
{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 5})},
},
updatedRes: &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Shares: uint64Ptr(1),
Period: uint64Ptr(10000),
Quota: int64Ptr(300000),
Cpus: "2",
Mems: "3",
},
},
wantUpdatedProps: []systemdDbus.Property{
// initial properties
{"CPUWeight", dbus.MakeVariant(convertCPUSharesToCgroupV2Value(1))},
{"CPUQuotaPeriodUSec", dbus.MakeVariant(uint64(20000))},
{"CPUQuotaPerSecUSec", dbus.MakeVariant(uint64(15000000))},
{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 4})},
{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 5})},
// updated properties
{"CPUWeight", dbus.MakeVariant(convertCPUSharesToCgroupV2Value(1))},
{"CPUQuotaPeriodUSec", dbus.MakeVariant(uint64(10000))},
{"CPUQuotaPerSecUSec", dbus.MakeVariant(uint64(30000000))},
{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 2})},
{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 3})},
},
},
{
name: "cpuset",
Expand Down Expand Up @@ -227,6 +253,18 @@ func TestInstall(t *testing.T) {
if diff := cmp.Diff(filteredProps, tc.wantProps, cmper, sorter); diff != "" {
t.Errorf("cgroup properties list diff %s", diff)
}

if tc.updatedRes != nil {
if err := cg.Update(tc.updatedRes); err != nil && err.Error() != dialErr {
if !errors.Is(err, tc.err) {
t.Fatalf("Wrong error, got: %s, want: %s", err, tc.err)
}
}
filteredProps = filterProperties(cg.properties, tc.wantUpdatedProps)
if diff := cmp.Diff(filteredProps, tc.wantUpdatedProps, cmper, sorter); diff != "" {
t.Errorf("cgroup properties list diff %s", diff)
}
}
})
}
}
Expand Down
1 change: 1 addition & 0 deletions runsc/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ func forEachCmd(cb func(cmd subcommands.Command, group string)) {
cb(new(cmd.Spec), "")
cb(new(cmd.Start), "")
cb(new(cmd.State), "")
cb(new(cmd.Update), "")
cb(new(cmd.Wait), "")

// Helpers.
Expand Down
1 change: 1 addition & 0 deletions runsc/cmd/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ go_library(
"symbolize.go",
"syscalls.go",
"umount_unsafe.go",
"update.go",
"usage.go",
"wait.go",
"write_control.go",
Expand Down
Loading