mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-07-31 23:36:12 +00:00
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 <julio.montes@intel.com>
This commit is contained in:
parent
0cea861f93
commit
81f376920e
@ -116,6 +116,7 @@ var runtimeCommands = []cli.Command{
|
||||
specCLICommand,
|
||||
startCLICommand,
|
||||
stateCLICommand,
|
||||
updateCLICommand,
|
||||
versionCLICommand,
|
||||
|
||||
// Kata Containers specific extensions
|
||||
|
261
cli/update.go
Normal file
261
cli/update.go
Normal file
@ -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: `<container-id>`,
|
||||
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)
|
||||
},
|
||||
}
|
205
cli/update_test.go
Normal file
205
cli/update_test.go
Normal file
@ -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)
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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...)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user