From 81f376920e6e073402e74a476cd5862640fff7dd Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Thu, 3 May 2018 15:20:51 -0500 Subject: [PATCH] cli: implement update command Update command is used to update container's resources at run time. All constraints are applied inside the VM to each container cgroup. By now only CPU constraints are fully supported, vCPU are hot added or removed depending of the new constraint. fixes #189 Signed-off-by: Julio Montes --- cli/main.go | 1 + cli/update.go | 261 +++++++++++++++++++++++++++ cli/update_test.go | 205 +++++++++++++++++++++ virtcontainers/agent.go | 4 + virtcontainers/api.go | 26 +++ virtcontainers/api_test.go | 46 +++++ virtcontainers/container.go | 70 +++++++ virtcontainers/hyperstart_agent.go | 6 + virtcontainers/implementation.go | 6 + virtcontainers/interfaces.go | 3 + virtcontainers/kata_agent.go | 18 ++ virtcontainers/kata_agent_test.go | 4 + virtcontainers/noop_agent.go | 7 + virtcontainers/pkg/vcmock/mock.go | 10 + virtcontainers/pkg/vcmock/sandbox.go | 6 + virtcontainers/pkg/vcmock/types.go | 2 + virtcontainers/sandbox.go | 12 ++ 17 files changed, 687 insertions(+) create mode 100644 cli/update.go create mode 100644 cli/update_test.go diff --git a/cli/main.go b/cli/main.go index 6101450806..b84d6fff19 100644 --- a/cli/main.go +++ b/cli/main.go @@ -116,6 +116,7 @@ var runtimeCommands = []cli.Command{ specCLICommand, startCLICommand, stateCLICommand, + updateCLICommand, versionCLICommand, // Kata Containers specific extensions diff --git a/cli/update.go b/cli/update.go new file mode 100644 index 0000000000..d1db024cf0 --- /dev/null +++ b/cli/update.go @@ -0,0 +1,261 @@ +// Copyright (c) 2016,2017 Docker, Inc. +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + + "github.com/docker/go-units" + vc "github.com/kata-containers/runtime/virtcontainers" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +func i64Ptr(i int64) *int64 { return &i } +func u64Ptr(i uint64) *uint64 { return &i } +func u16Ptr(i uint16) *uint16 { return &i } + +var updateCLICommand = cli.Command{ + Name: "update", + Usage: "update container resource constraints", + ArgsUsage: ``, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "resources, r", + Value: "", + Usage: `path to the file containing the resources to update or '-' to read from the standard input + +The accepted format is as follow (unchanged values can be omitted): + +{ + "memory": { + "limit": 0, + "reservation": 0, + "swap": 0, + "kernel": 0, + "kernelTCP": 0 + }, + "cpu": { + "shares": 0, + "quota": 0, + "period": 0, + "realtimeRuntime": 0, + "realtimePeriod": 0, + "cpus": "", + "mems": "" + }, + "blockIO": { + "weight": 0 + }, + "pids": { + "limit": 0 + } +} + +Note: if data is to be read from a file or the standard input, all +other options are ignored. +`, + }, + + cli.IntFlag{ + Name: "blkio-weight", + Usage: "Specifies per cgroup weight, range is from 10 to 1000", + }, + cli.StringFlag{ + Name: "cpu-period", + Usage: "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default", + }, + cli.StringFlag{ + Name: "cpu-quota", + Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period", + }, + cli.StringFlag{ + Name: "cpu-share", + Usage: "CPU shares (relative weight vs. other containers)", + }, + cli.StringFlag{ + Name: "cpu-rt-period", + Usage: "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default", + }, + cli.StringFlag{ + Name: "cpu-rt-runtime", + Usage: "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period", + }, + cli.StringFlag{ + Name: "cpuset-cpus", + Usage: "CPU(s) to use", + }, + cli.StringFlag{ + Name: "cpuset-mems", + Usage: "Memory node(s) to use", + }, + cli.StringFlag{ + Name: "kernel-memory", + Usage: "Kernel memory limit (in bytes)", + }, + cli.StringFlag{ + Name: "kernel-memory-tcp", + Usage: "Kernel memory limit (in bytes) for tcp buffer", + }, + cli.StringFlag{ + Name: "memory", + Usage: "Memory limit (in bytes)", + }, + cli.StringFlag{ + Name: "memory-reservation", + Usage: "Memory reservation or soft_limit (in bytes)", + }, + cli.StringFlag{ + Name: "memory-swap", + Usage: "Total memory usage (memory + swap); set '-1' to enable unlimited swap", + }, + cli.IntFlag{ + Name: "pids-limit", + Usage: "Maximum number of pids allowed in the container", + }, + cli.StringFlag{ + Name: "l3-cache-schema", + Usage: "The string of Intel RDT/CAT L3 cache schema", + }, + }, + Action: func(context *cli.Context) error { + if context.Args().Present() == false { + return fmt.Errorf("Missing container ID, should at least provide one") + } + + containerID := context.Args().First() + status, sandboxID, err := getExistingContainerInfo(containerID) + if err != nil { + return err + } + + containerID = status.ID + // container MUST be running + if status.State.State != vc.StateRunning { + return fmt.Errorf("Container %s is not running", containerID) + } + + r := specs.LinuxResources{ + Memory: &specs.LinuxMemory{ + Limit: i64Ptr(0), + Reservation: i64Ptr(0), + Swap: i64Ptr(0), + Kernel: i64Ptr(0), + KernelTCP: i64Ptr(0), + }, + CPU: &specs.LinuxCPU{ + Shares: u64Ptr(0), + Quota: i64Ptr(0), + Period: u64Ptr(0), + RealtimeRuntime: i64Ptr(0), + RealtimePeriod: u64Ptr(0), + Cpus: "", + Mems: "", + }, + BlockIO: &specs.LinuxBlockIO{ + Weight: u16Ptr(0), + }, + Pids: &specs.LinuxPids{ + Limit: 0, + }, + } + + if in := context.String("resources"); in != "" { + var ( + f *os.File + err error + ) + switch in { + case "-": + f = os.Stdin + default: + f, err = os.Open(in) + if err != nil { + return err + } + } + err = json.NewDecoder(f).Decode(&r) + if err != nil { + return err + } + } else { + if val := context.Int("blkio-weight"); val != 0 { + r.BlockIO.Weight = u16Ptr(uint16(val)) + } + if val := context.String("cpuset-cpus"); val != "" { + r.CPU.Cpus = val + } + if val := context.String("cpuset-mems"); val != "" { + r.CPU.Mems = val + } + + for _, pair := range []struct { + opt string + dest *uint64 + }{ + + {"cpu-period", r.CPU.Period}, + {"cpu-rt-period", r.CPU.RealtimePeriod}, + {"cpu-share", r.CPU.Shares}, + } { + if val := context.String(pair.opt); val != "" { + var err error + *pair.dest, err = strconv.ParseUint(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", pair.opt, err) + } + } + } + for _, pair := range []struct { + opt string + dest *int64 + }{ + + {"cpu-quota", r.CPU.Quota}, + {"cpu-rt-runtime", r.CPU.RealtimeRuntime}, + } { + if val := context.String(pair.opt); val != "" { + var err error + *pair.dest, err = strconv.ParseInt(val, 10, 64) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", pair.opt, err) + } + } + } + for _, pair := range []struct { + opt string + dest *int64 + }{ + {"memory", r.Memory.Limit}, + {"memory-swap", r.Memory.Swap}, + {"kernel-memory", r.Memory.Kernel}, + {"kernel-memory-tcp", r.Memory.KernelTCP}, + {"memory-reservation", r.Memory.Reservation}, + } { + if val := context.String(pair.opt); val != "" { + var v int64 + + if val != "-1" { + v, err = units.RAMInBytes(val) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", pair.opt, err) + } + } else { + v = -1 + } + *pair.dest = v + } + } + r.Pids.Limit = int64(context.Int("pids-limit")) + } + + return vci.UpdateContainer(sandboxID, containerID, r) + }, +} diff --git a/cli/update_test.go b/cli/update_test.go new file mode 100644 index 0000000000..e2c2e38ce1 --- /dev/null +++ b/cli/update_test.go @@ -0,0 +1,205 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + vc "github.com/kata-containers/runtime/virtcontainers" + vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations" + "github.com/kata-containers/runtime/virtcontainers/pkg/vcmock" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestUpdateCLIAction(t *testing.T) { + assert := assert.New(t) + + flagSet := flag.NewFlagSet("update", flag.ContinueOnError) + flagSet.Parse([]string{"resources"}) + + // create a new fake context + ctx := cli.NewContext(&cli.App{}, flagSet, nil) + + // get Action function + actionFunc, ok := updateCLICommand.Action.(func(ctx *cli.Context) error) + assert.True(ok) + + err := actionFunc(ctx) + assert.Error(err, "Missing container ID") +} + +func TestUpdateCLIFailure(t *testing.T) { + assert := assert.New(t) + + flagSet := flag.NewFlagSet("update", flag.ContinueOnError) + ctx := cli.NewContext(&cli.App{}, flagSet, nil) + + actionFunc, ok := updateCLICommand.Action.(func(ctx *cli.Context) error) + assert.True(ok) + + // missing container ID + err := actionFunc(ctx) + assert.Error(err) + + // container info + flagSet.Parse([]string{testContainerID}) + ctx = cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.Error(err) + + // not running + sandbox := &vcmock.Sandbox{ + MockID: testContainerID, + } + + sandbox.MockContainers = []*vcmock.Container{ + { + MockID: sandbox.ID(), + MockSandbox: sandbox, + }, + } + + path, err := createTempContainerIDMapping(sandbox.ID(), sandbox.ID()) + assert.NoError(err) + defer os.RemoveAll(path) + + testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: sandbox.ID(), + Annotations: map[string]string{ + vcAnnotations.ContainerTypeKey: string(vc.PodContainer), + }, + }, nil + } + + defer func() { + testingImpl.StatusContainerFunc = nil + }() + err = actionFunc(ctx) + assert.Error(err) + + // resources file does not exist + testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: sandbox.ID(), + Annotations: map[string]string{ + vcAnnotations.ContainerTypeKey: string(vc.PodContainer), + }, + State: vc.State{ + State: vc.StateRunning, + }, + }, nil + } + testingImpl.UpdateContainerFunc = func(sandboxID, containerID string, resources specs.LinuxResources) error { + return nil + } + defer func() { + testingImpl.UpdateContainerFunc = nil + }() + flagSet.String("resources", "/abc/123/xyz/rgb", "") + ctx = cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.Error(err) + + // json decode error + f, err := ioutil.TempFile("", "resources") + assert.NoError(err) + assert.NotNil(f) + f.WriteString("no json") + f.Close() + flagSet.Set("resources", f.Name()) + ctx = cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.Error(err) + + // ParseUint Error + flagSet = flag.NewFlagSet("update", flag.ContinueOnError) + flagSet.Parse([]string{testContainerID}) + flagSet.String("cpu-period", "abcxyz", "") + ctx = cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.Error(err) + + // ParseInt Error + flagSet = flag.NewFlagSet("update", flag.ContinueOnError) + flagSet.Parse([]string{testContainerID}) + flagSet.String("cpu-quota", "abcxyz", "") + ctx = cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.Error(err) + + // RAMInBytes Error + flagSet = flag.NewFlagSet("update", flag.ContinueOnError) + flagSet.Parse([]string{testContainerID}) + flagSet.String("memory", "abcxyz", "") + ctx = cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.Error(err) +} + +func TestUpdateCLISuccessful(t *testing.T) { + assert := assert.New(t) + + sandbox := &vcmock.Sandbox{ + MockID: testContainerID, + } + + sandbox.MockContainers = []*vcmock.Container{ + { + MockID: sandbox.ID(), + MockSandbox: sandbox, + }, + } + + testingImpl.StatusContainerFunc = func(sandboxID, containerID string) (vc.ContainerStatus, error) { + return vc.ContainerStatus{ + ID: sandbox.ID(), + Annotations: map[string]string{ + vcAnnotations.ContainerTypeKey: string(vc.PodContainer), + }, + State: vc.State{ + State: vc.StateRunning, + }, + }, nil + } + testingImpl.UpdateContainerFunc = func(sandboxID, containerID string, resources specs.LinuxResources) error { + return nil + } + defer func() { + testingImpl.StatusContainerFunc = nil + testingImpl.UpdateContainerFunc = nil + }() + + path, err := createTempContainerIDMapping(sandbox.ID(), sandbox.ID()) + assert.NoError(err) + defer os.RemoveAll(path) + actionFunc, ok := updateCLICommand.Action.(func(ctx *cli.Context) error) + assert.True(ok) + + flagSet := flag.NewFlagSet("update", flag.ContinueOnError) + flagSet.Parse([]string{testContainerID}) + flagSet.Int("blkio-weight", 20, "") + flagSet.String("cpuset-cpus", "0-5", "") + flagSet.String("cpuset-mems", "0-5", "") + flagSet.String("cpu-period", "1000", "") + flagSet.String("cpu-rt-period", "1000", "") + flagSet.String("cpu-share", "1000", "") + flagSet.String("cpu-quota", "1000", "") + flagSet.String("cpu-rt-runtime", "1000", "") + flagSet.String("memory", "100M", "") + flagSet.String("memory-swap", "100M", "") + flagSet.String("kernel-memory", "100M", "") + flagSet.String("kernel-memory-tcp", "100M", "") + flagSet.String("memory-reservation", "100M", "") + ctx := cli.NewContext(&cli.App{}, flagSet, nil) + err = actionFunc(ctx) + assert.NoError(err) +} diff --git a/virtcontainers/agent.go b/virtcontainers/agent.go index 568708e3bb..a787ade7b0 100644 --- a/virtcontainers/agent.go +++ b/virtcontainers/agent.go @@ -10,6 +10,7 @@ import ( "syscall" "github.com/mitchellh/mapstructure" + specs "github.com/opencontainers/runtime-spec/specs-go" ) // AgentType describes the type of guest agent a Sandbox should run. @@ -183,6 +184,9 @@ type agent interface { // processListContainer will list the processes running inside the container processListContainer(sandbox *Sandbox, c Container, options ProcessListOptions) (ProcessList, error) + // updateContainer will update the resources of a running container + updateContainer(sandbox *Sandbox, c Container, resources specs.LinuxResources) error + // waitProcess will wait for the exit code of a process waitProcess(c *Container, processID string) (int32, error) diff --git a/virtcontainers/api.go b/virtcontainers/api.go index 72687bda61..5cfb5a23f1 100644 --- a/virtcontainers/api.go +++ b/virtcontainers/api.go @@ -11,6 +11,7 @@ import ( "syscall" deviceApi "github.com/kata-containers/runtime/virtcontainers/device/api" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -602,3 +603,28 @@ func ProcessListContainer(sandboxID, containerID string, options ProcessListOpti return c.processList(options) } + +// UpdateContainer is the virtcontainers entry point to update +// container's resources. +func UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error { + if sandboxID == "" { + return errNeedSandboxID + } + + if containerID == "" { + return errNeedContainerID + } + + lockFile, err := rLockSandbox(sandboxID) + if err != nil { + return err + } + defer unlockSandbox(lockFile) + + s, err := fetchSandbox(sandboxID) + if err != nil { + return err + } + + return s.UpdateContainer(containerID, resources) +} diff --git a/virtcontainers/api_test.go b/virtcontainers/api_test.go index 2ea03c0958..0a009f53e7 100644 --- a/virtcontainers/api_test.go +++ b/virtcontainers/api_test.go @@ -17,6 +17,7 @@ import ( "testing" "github.com/kata-containers/runtime/virtcontainers/pkg/mock" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/stretchr/testify/assert" ) @@ -2286,3 +2287,48 @@ func TestReleaseSandbox(t *testing.T) { err = s.Release() assert.Nil(t, err, "sandbox release failed: %v", err) } + +func TestUpdateContainer(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip(testDisabledAsNonRoot) + } + + cleanUp() + + period := uint64(1000) + quota := int64(2000) + assert := assert.New(t) + resources := specs.LinuxResources{ + CPU: &specs.LinuxCPU{ + Period: &period, + Quota: "a, + }, + } + err := UpdateContainer("", "", resources) + assert.Error(err) + + err = UpdateContainer("abc", "", resources) + assert.Error(err) + + contID := "100" + config := newTestSandboxConfigNoop() + + s, sandboxDir, err := createAndStartSandbox(config) + assert.NoError(err) + assert.NotNil(s) + + contConfig := newTestContainerConfigNoop(contID) + _, c, err := CreateContainer(s.ID(), contConfig) + assert.NoError(err) + assert.NotNil(c) + + contDir := filepath.Join(sandboxDir, contID) + _, err = os.Stat(contDir) + assert.NoError(err) + + _, err = StartContainer(s.ID(), contID) + assert.NoError(err) + + err = UpdateContainer(s.ID(), contID, resources) + assert.NoError(err) +} diff --git a/virtcontainers/container.go b/virtcontainers/container.go index c4574fe5e4..f96cc61d37 100644 --- a/virtcontainers/container.go +++ b/virtcontainers/container.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -787,6 +788,33 @@ func (c *Container) processList(options ProcessListOptions) (ProcessList, error) return c.sandbox.agent.processListContainer(c.sandbox, *c, options) } +func (c *Container) update(resources specs.LinuxResources) error { + if err := c.checkSandboxRunning("update"); err != nil { + return err + } + + if c.state.State != StateRunning { + return fmt.Errorf("Container not running, impossible to update") + } + + // fetch current configuration + currentConfig, err := c.sandbox.storage.fetchContainerConfig(c.sandbox.id, c.id) + if err != nil { + return err + } + + newResources := ContainerResources{ + CPUPeriod: *resources.CPU.Period, + CPUQuota: *resources.CPU.Quota, + } + + if err := c.updateResources(currentConfig.Resources, newResources); err != nil { + return err + } + + return c.sandbox.agent.updateContainer(c.sandbox, *c, resources) +} + func (c *Container) hotplugDrive() error { dev, err := getDeviceForPath(c.rootFs) @@ -939,3 +967,45 @@ func (c *Container) removeResources() error { return nil } + +func (c *Container) updateResources(oldResources, newResources ContainerResources) error { + //TODO add support for memory, Issue: https://github.com/containers/virtcontainers/issues/578 + var vCPUs uint + oldVCPUs := utils.ConstraintsToVCPUs(oldResources.CPUQuota, oldResources.CPUPeriod) + newVCPUs := utils.ConstraintsToVCPUs(newResources.CPUQuota, newResources.CPUPeriod) + + // Update vCPUs is not possible if period and/or quota are not set or + // oldVCPUs and newVCPUs are equal. + // Don't fail, the constraint still can be applied in the cgroup. + if newVCPUs == 0 || oldVCPUs == newVCPUs { + c.Logger().WithFields(logrus.Fields{ + "old-vcpus": fmt.Sprintf("%d", oldVCPUs), + "new-vcpus": fmt.Sprintf("%d", newVCPUs), + }).Debug("the actual number of vCPUs will not be modified") + return nil + } + + if oldVCPUs < newVCPUs { + // hot add vCPUs + vCPUs = newVCPUs - oldVCPUs + virtLog.Debugf("hot adding %d vCPUs", vCPUs) + if err := c.sandbox.hypervisor.hotplugAddDevice(uint32(vCPUs), cpuDev); err != nil { + return err + } + } else { + // hot remove vCPUs + vCPUs = oldVCPUs - newVCPUs + virtLog.Debugf("hot removing %d vCPUs", vCPUs) + if err := c.sandbox.hypervisor.hotplugRemoveDevice(uint32(vCPUs), cpuDev); err != nil { + return err + } + } + + // Set and save container's config + c.config.Resources = newResources + if err := c.storeContainer(); err != nil { + return err + } + + return c.sandbox.agent.onlineCPUMem(uint32(vCPUs)) +} diff --git a/virtcontainers/hyperstart_agent.go b/virtcontainers/hyperstart_agent.go index 8e8e635dab..6c82025775 100644 --- a/virtcontainers/hyperstart_agent.go +++ b/virtcontainers/hyperstart_agent.go @@ -19,6 +19,7 @@ import ( "github.com/kata-containers/runtime/virtcontainers/pkg/hyperstart" ns "github.com/kata-containers/runtime/virtcontainers/pkg/nsenter" "github.com/kata-containers/runtime/virtcontainers/utils" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -608,6 +609,11 @@ func (h *hyper) processListContainer(sandbox *Sandbox, c Container, options Proc return h.processListOneContainer(sandbox.id, c.id, options) } +func (h *hyper) updateContainer(sandbox *Sandbox, c Container, resources specs.LinuxResources) error { + // hyperstart-agent does not support update + return nil +} + func (h *hyper) processListOneContainer(sandboxID, cID string, options ProcessListOptions) (ProcessList, error) { psCmd := hyperstart.PsCommand{ Container: cID, diff --git a/virtcontainers/implementation.go b/virtcontainers/implementation.go index 229feca7de..dba32c5063 100644 --- a/virtcontainers/implementation.go +++ b/virtcontainers/implementation.go @@ -12,6 +12,7 @@ package virtcontainers import ( "syscall" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -114,3 +115,8 @@ func (impl *VCImpl) KillContainer(sandboxID, containerID string, signal syscall. func (impl *VCImpl) ProcessListContainer(sandboxID, containerID string, options ProcessListOptions) (ProcessList, error) { return ProcessListContainer(sandboxID, containerID, options) } + +// UpdateContainer implements the VC function of the same name. +func (impl *VCImpl) UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error { + return UpdateContainer(sandboxID, containerID, resources) +} diff --git a/virtcontainers/interfaces.go b/virtcontainers/interfaces.go index fe2b2bb468..c86736232f 100644 --- a/virtcontainers/interfaces.go +++ b/virtcontainers/interfaces.go @@ -9,6 +9,7 @@ import ( "io" "syscall" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -35,6 +36,7 @@ type VC interface { StatusContainer(sandboxID, containerID string) (ContainerStatus, error) StopContainer(sandboxID, containerID string) (VCContainer, error) ProcessListContainer(sandboxID, containerID string, options ProcessListOptions) (ProcessList, error) + UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error } // VCSandbox is the Sandbox interface @@ -58,6 +60,7 @@ type VCSandbox interface { StartContainer(containerID string) (VCContainer, error) StatusContainer(containerID string) (ContainerStatus, error) EnterContainer(containerID string, cmd Cmd) (VCContainer, *Process, error) + UpdateContainer(containerID string, resources specs.LinuxResources) error WaitProcess(containerID, processID string) (int32, error) SignalProcess(containerID, processID string, signal syscall.Signal, all bool) error WinsizeProcess(containerID, processID string, height, width uint32) error diff --git a/virtcontainers/kata_agent.go b/virtcontainers/kata_agent.go index 155b4130bc..4e835200f1 100644 --- a/virtcontainers/kata_agent.go +++ b/virtcontainers/kata_agent.go @@ -911,6 +911,21 @@ func (k *kataAgent) processListContainer(sandbox *Sandbox, c Container, options return processList.ProcessList, nil } +func (k *kataAgent) updateContainer(sandbox *Sandbox, c Container, resources specs.LinuxResources) error { + grpcResources, err := grpc.ResourcesOCItoGRPC(&resources) + if err != nil { + return err + } + + req := &grpc.UpdateContainerRequest{ + ContainerId: c.id, + Resources: grpcResources, + } + + _, err = k.sendReq(req) + return err +} + func (k *kataAgent) onlineCPUMem(cpus uint32) error { req := &grpc.OnlineCPUMemRequest{ Wait: false, @@ -1034,6 +1049,9 @@ func (k *kataAgent) installReqFunc(c *kataclient.AgentClient) { k.reqHandlers["grpc.ListProcessesRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) { return k.client.ListProcesses(ctx, req.(*grpc.ListProcessesRequest), opts...) } + k.reqHandlers["grpc.UpdateContainerRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) { + return k.client.UpdateContainer(ctx, req.(*grpc.UpdateContainerRequest), opts...) + } k.reqHandlers["grpc.WaitProcessRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) { return k.client.WaitProcess(ctx, req.(*grpc.WaitProcessRequest), opts...) } diff --git a/virtcontainers/kata_agent_test.go b/virtcontainers/kata_agent_test.go index 2ddb33b8c9..df87ffa73f 100644 --- a/virtcontainers/kata_agent_test.go +++ b/virtcontainers/kata_agent_test.go @@ -143,6 +143,10 @@ func (p *gRPCProxy) ListProcesses(ctx context.Context, req *pb.ListProcessesRequ return &pb.ListProcessesResponse{}, nil } +func (p *gRPCProxy) UpdateContainer(ctx context.Context, req *pb.UpdateContainerRequest) (*gpb.Empty, error) { + return emptyResp, nil +} + func (p *gRPCProxy) RemoveContainer(ctx context.Context, req *pb.RemoveContainerRequest) (*gpb.Empty, error) { return emptyResp, nil } diff --git a/virtcontainers/noop_agent.go b/virtcontainers/noop_agent.go index c6905f8090..e437e62c5b 100644 --- a/virtcontainers/noop_agent.go +++ b/virtcontainers/noop_agent.go @@ -7,6 +7,8 @@ package virtcontainers import ( "syscall" + + specs "github.com/opencontainers/runtime-spec/specs-go" ) // noopAgent a.k.a. NO-OP Agent is an empty Agent implementation, for testing and @@ -74,6 +76,11 @@ func (n *noopAgent) processListContainer(sandbox *Sandbox, c Container, options return nil, nil } +// updateContainer is the Noop agent Container update implementation. It does nothing. +func (n *noopAgent) updateContainer(sandbox *Sandbox, c Container, resources specs.LinuxResources) error { + return nil +} + // onlineCPUMem is the Noop agent Container online CPU and Memory implementation. It does nothing. func (n *noopAgent) onlineCPUMem(cpus uint32) error { return nil diff --git a/virtcontainers/pkg/vcmock/mock.go b/virtcontainers/pkg/vcmock/mock.go index a494014efb..df9e0170f0 100644 --- a/virtcontainers/pkg/vcmock/mock.go +++ b/virtcontainers/pkg/vcmock/mock.go @@ -20,6 +20,7 @@ import ( "syscall" vc "github.com/kata-containers/runtime/virtcontainers" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -195,3 +196,12 @@ func (m *VCMock) ProcessListContainer(sandboxID, containerID string, options vc. return nil, fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID) } + +// UpdateContainer implements the VC function of the same name. +func (m *VCMock) UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error { + if m.UpdateContainerFunc != nil { + return m.UpdateContainerFunc(sandboxID, containerID, resources) + } + + return fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID) +} diff --git a/virtcontainers/pkg/vcmock/sandbox.go b/virtcontainers/pkg/vcmock/sandbox.go index 720e2d3afe..25a44c94f6 100644 --- a/virtcontainers/pkg/vcmock/sandbox.go +++ b/virtcontainers/pkg/vcmock/sandbox.go @@ -10,6 +10,7 @@ import ( "syscall" vc "github.com/kata-containers/runtime/virtcontainers" + specs "github.com/opencontainers/runtime-spec/specs-go" ) // ID implements the VCSandbox function of the same name. @@ -108,6 +109,11 @@ func (p *Sandbox) Monitor() (chan error, error) { return nil, nil } +// UpdateContainer implements the VCSandbox function of the same name. +func (p *Sandbox) UpdateContainer(containerID string, resources specs.LinuxResources) error { + return nil +} + // WaitProcess implements the VCSandbox function of the same name. func (p *Sandbox) WaitProcess(containerID, processID string) (int32, error) { return 0, nil diff --git a/virtcontainers/pkg/vcmock/types.go b/virtcontainers/pkg/vcmock/types.go index cf67b1425a..36a6eb38ec 100644 --- a/virtcontainers/pkg/vcmock/types.go +++ b/virtcontainers/pkg/vcmock/types.go @@ -9,6 +9,7 @@ import ( "syscall" vc "github.com/kata-containers/runtime/virtcontainers" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -55,4 +56,5 @@ type VCMock struct { StatusContainerFunc func(sandboxID, containerID string) (vc.ContainerStatus, error) StopContainerFunc func(sandboxID, containerID string) (vc.VCContainer, error) ProcessListContainerFunc func(sandboxID, containerID string, options vc.ProcessListOptions) (vc.ProcessList, error) + UpdateContainerFunc func(sandboxID, containerID string, resources specs.LinuxResources) error } diff --git a/virtcontainers/sandbox.go b/virtcontainers/sandbox.go index 89ae785510..823f4bbe4b 100644 --- a/virtcontainers/sandbox.go +++ b/virtcontainers/sandbox.go @@ -14,6 +14,7 @@ import ( "sync" "syscall" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/kata-containers/runtime/virtcontainers/device/api" @@ -1089,6 +1090,17 @@ func (s *Sandbox) EnterContainer(containerID string, cmd Cmd) (VCContainer, *Pro return c, process, nil } +// UpdateContainer update a running container. +func (s *Sandbox) UpdateContainer(containerID string, resources specs.LinuxResources) error { + // Fetch the container. + c, err := s.findContainer(containerID) + if err != nil { + return err + } + + return c.update(resources) +} + // createContainers registers all containers to the proxy, create the // containers in the guest and starts one shim per container. func (s *Sandbox) createContainers() error {