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 {