diff --git a/cli/update.go b/cli/update.go index c2e076d8c..8d37bba97 100644 --- a/cli/update.go +++ b/cli/update.go @@ -41,6 +41,7 @@ func (uc *UpdateCommand) addFlags() { flagSet := uc.cmd.Flags() flagSet.SetInterspersed(false) flagSet.Uint16Var(&uc.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable") + flagSet.Int64Var(&uc.cpuperiod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period, range is in [1000(1ms),1000000(1s)]") flagSet.Int64Var(&uc.cpushare, "cpu-share", 0, "CPU shares (relative weight)") flagSet.StringVar(&uc.cpusetcpus, "cpuset-cpus", "", "CPUs in cpuset") flagSet.StringVar(&uc.cpusetmems, "cpuset-mems", "", "MEMs in cpuset") @@ -77,6 +78,7 @@ func (uc *UpdateCommand) updateRun(args []string) error { } resource := types.Resources{ + CPUPeriod: uc.cpuperiod, CPUShares: uc.cpushare, CpusetCpus: uc.cpusetcpus, CpusetMems: uc.cpusetmems, diff --git a/ctrd/utils.go b/ctrd/utils.go index 680c06c8f..6c06b4cda 100644 --- a/ctrd/utils.go +++ b/ctrd/utils.go @@ -146,10 +146,13 @@ func toLinuxResources(resources types.Resources) (*specs.LinuxResources, error) // toLinuxCPU shares := uint64(resources.CPUShares) + period := uint64(resources.CPUPeriod) r.CPU = &specs.LinuxCPU{ Cpus: resources.CpusetCpus, Mems: resources.CpusetMems, Shares: &shares, + Period: &period, + Quota: &resources.CPUQuota, } // toLinuxMemory @@ -158,9 +161,10 @@ func toLinuxResources(resources types.Resources) (*specs.LinuxResources, error) swappiness = uint64(*(resources.MemorySwappiness)) } r.Memory = &specs.LinuxMemory{ - Limit: &resources.Memory, - Swap: &resources.MemorySwap, - Swappiness: &swappiness, + Limit: &resources.Memory, + Swap: &resources.MemorySwap, + Swappiness: &swappiness, + Reservation: &resources.MemoryReservation, } // TODO: add more fields. diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index 7b255d2c4..82a97fd3f 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -877,6 +877,10 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty c.Lock() defer c.Unlock() + if c.IsRunning() && config.Resources.KernelMemory != 0 { + return fmt.Errorf("failed to update container %s: can not update kernel memory to a running container, please stop it first", c.ID()) + } + if len(config.Labels) != 0 { if c.meta.Config.Labels == nil { c.meta.Config.Labels = map[string]string{} @@ -887,29 +891,11 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty } } - // update resources of container. - resources := config.Resources - cResources := &c.meta.HostConfig.Resources - if resources.BlkioWeight != 0 { - cResources.BlkioWeight = resources.BlkioWeight - } - if resources.CPUShares != 0 { - cResources.CPUShares = resources.CPUShares - } - if resources.CpusetCpus != "" { - cResources.CpusetCpus = resources.CpusetCpus - } - if resources.CpusetMems != "" { - cResources.CpusetMems = resources.CpusetMems - } - if resources.Memory != 0 { - cResources.Memory = resources.Memory - } - if resources.MemorySwap != 0 { - cResources.MemorySwap = resources.MemorySwap + // update Resources of a container. + if err := mgr.updateContainerResources(c.meta, config.Resources); err != nil { + return fmt.Errorf("failed to update container %s resources: %v", c.ID(), err) } - // update HostConfig of a container. // TODO update restartpolicy when container is running. if config.RestartPolicy.Name != "" { c.meta.HostConfig.RestartPolicy = config.RestartPolicy @@ -945,6 +931,53 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty return updateErr } +// updateContainerResources update container's resources parameters. +func (mgr *ContainerManager) updateContainerResources(c *ContainerMeta, resources types.Resources) error { + // update resources of container. + cResources := &c.HostConfig.Resources + if resources.BlkioWeight != 0 { + cResources.BlkioWeight = resources.BlkioWeight + } + if resources.CPUPeriod != 0 { + cResources.CPUPeriod = resources.CPUPeriod + } + if resources.CPUQuota != 0 { + cResources.CPUQuota = resources.CPUQuota + } + if resources.CPUShares != 0 { + cResources.CPUShares = resources.CPUShares + } + if resources.CpusetCpus != "" { + cResources.CpusetCpus = resources.CpusetCpus + } + if resources.CpusetMems != "" { + cResources.CpusetMems = resources.CpusetMems + } + if resources.Memory != 0 { + // if memory limit smaller than already set memoryswap limit and doesn't + // update the memoryswap limit, then error out. + if cResources.MemorySwap != 0 && resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 { + return fmt.Errorf("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time") + } + cResources.Memory = resources.Memory + } + if resources.MemorySwap != 0 { + cResources.MemorySwap = resources.MemorySwap + } + + if resources.MemorySwap != 0 { + cResources.MemorySwap = resources.MemorySwap + } + if resources.MemoryReservation != 0 { + cResources.MemoryReservation = resources.MemoryReservation + } + if resources.KernelMemory != 0 { + cResources.KernelMemory = resources.KernelMemory + } + + return nil +} + // updateContainerEnv update the container's envs in /etc/instanceInfo and /etc/profile.d/pouchenv.sh // Env used by rich container. func (mgr *ContainerManager) updateContainerEnv(containerEnvs []string, baseFs string) error { diff --git a/test/cli_update_test.go b/test/cli_update_test.go index 78d1ef445..a7675934c 100644 --- a/test/cli_update_test.go +++ b/test/cli_update_test.go @@ -75,6 +75,46 @@ func (suite *PouchUpdateSuite) TestUpdateCpu(c *check.C) { command.PouchRun("rm", "-f", name).Assert(c, icmd.Success) } +// TestUpdateCpuPeriod is to verify the correctness of updating container cpu-period. +func (suite *PouchUpdateSuite) TestUpdateCpuPeriod(c *check.C) { + name := "update-container-cpu-period" + + command.PouchRun("run", "-d", "--name", name, busyboxImage, "top").Assert(c, icmd.Success) + + output := command.PouchRun("inspect", name).Stdout() + result := []types.ContainerJSON{} + if err := json.Unmarshal([]byte(output), &result); err != nil { + c.Errorf("failed to decode inspect output: %v", err) + } + containerID := result[0].ID + + file := "/sys/fs/cgroup/cpu/default/" + containerID + "/cpu.cfs_period_us" + if _, err := os.Stat(file); err != nil { + c.Fatalf("container %s cgroup mountpoint not exists", containerID) + } + + command.PouchRun("update", "--cpu-period", "2000", name).Assert(c, icmd.Success) + + out, err := exec.Command("cat", file).Output() + if err != nil { + c.Fatalf("execute cat command failed: %v", err) + } + + if !strings.Contains(string(out), "2000") { + c.Fatalf("unexpected output %s expected %s\n", string(out), "2000") + } + + inspectInfo := command.PouchRun("inspect", name).Stdout() + metaJSON := []types.ContainerJSON{} + if err := json.Unmarshal([]byte(inspectInfo), &metaJSON); err != nil { + c.Errorf("failed to decode inspect output: %v", err) + } + + c.Assert(metaJSON[0].HostConfig.CPUPeriod, check.Equals, int64(2000)) + + command.PouchRun("rm", "-f", name).Assert(c, icmd.Success) +} + // TestUpdateRunningContainer is to verify the correctness of updating a running container. func (suite *PouchUpdateSuite) TestUpdateRunningContainer(c *check.C) { name := "update-running-container"