Files
runc/cgroups/fs/apply_raw.go
T
Zefan Li f112d83776 Avoid trying to access cpu.shares when it doesn't exist
Even if cpu cgroup controller is enabled it's still possible that
cpu.shares doesn't exist.

This is the case when the kernel config has CONFIG_CGROUP_SCHED
enabled but CONFIG_FAIR_GROUP_SCHED disabled. Then docker fails to
start containers even --cpu-shares isn't specified.

$ sudo docker run -i -t ubuntu:14.04
Error response from daemon: Cannot start container 5600ae87eb9d0eca49f6bcee012247d6b4beb49c426d6cf17e2456279f9311f6: [8] System error: open /sys/fs/cgroup/cpu/docker/5600ae87eb9d0eca49f6bcee012247d6b4beb49c426d6cf17e2456279f9311f6/cpu.shares: no such file or directory

Signed-off-by: Zefan Li <lizefan@huawei.com>
2015-06-18 21:15:52 +08:00

330 lines
6.6 KiB
Go

// +build linux
package fs
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
)
var (
subsystems = map[string]subsystem{
"devices": &DevicesGroup{},
"memory": &MemoryGroup{},
"cpu": &CpuGroup{},
"cpuset": &CpusetGroup{},
"cpuacct": &CpuacctGroup{},
"blkio": &BlkioGroup{},
"hugetlb": &HugetlbGroup{},
"net_cls": &NetClsGroup{},
"net_prio": &NetPrioGroup{},
"perf_event": &PerfEventGroup{},
"freezer": &FreezerGroup{},
}
CgroupProcesses = "cgroup.procs"
HugePageSizes, _ = cgroups.GetHugePageSize()
)
type subsystem interface {
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Removes the cgroup represented by 'data'.
Remove(*data) error
// Creates and joins the cgroup represented by data.
Apply(*data) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
}
type Manager struct {
mu sync.Mutex
Cgroups *configs.Cgroup
Paths map[string]string
}
// The absolute path to the root of the cgroup hierarchies.
var cgroupRootLock sync.Mutex
var cgroupRoot string
// Gets the cgroupRoot.
func getCgroupRoot() (string, error) {
cgroupRootLock.Lock()
defer cgroupRootLock.Unlock()
if cgroupRoot != "" {
return cgroupRoot, nil
}
root, err := cgroups.FindCgroupMountpointDir()
if err != nil {
return "", err
}
if _, err := os.Stat(root); err != nil {
return "", err
}
cgroupRoot = root
return cgroupRoot, nil
}
type data struct {
root string
cgroup string
c *configs.Cgroup
pid int
}
func (m *Manager) Apply(pid int) error {
if m.Cgroups == nil {
return nil
}
var c = m.Cgroups
d, err := getCgroupData(m.Cgroups, pid)
if err != nil {
return err
}
paths := make(map[string]string)
defer func() {
if err != nil {
cgroups.RemovePaths(paths)
}
}()
for name, sys := range subsystems {
if err := sys.Apply(d); err != nil {
return err
}
// TODO: Apply should, ideally, be reentrant or be broken up into a separate
// create and join phase so that the cgroup hierarchy for a container can be
// created then join consists of writing the process pids to cgroup.procs
p, err := d.path(name)
if err != nil {
if cgroups.IsNotFound(err) {
continue
}
return err
}
paths[name] = p
}
m.Paths = paths
if paths["cpu"] != "" {
if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
return err
}
}
return nil
}
func (m *Manager) Destroy() error {
m.mu.Lock()
defer m.mu.Unlock()
if err := cgroups.RemovePaths(m.Paths); err != nil {
return err
}
m.Paths = make(map[string]string)
return nil
}
func (m *Manager) GetPaths() map[string]string {
m.mu.Lock()
paths := m.Paths
m.mu.Unlock()
return paths
}
func (m *Manager) GetStats() (*cgroups.Stats, error) {
m.mu.Lock()
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
continue
}
if err := sys.GetStats(path, stats); err != nil {
return nil, err
}
}
return stats, nil
}
func (m *Manager) Set(container *configs.Config) error {
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
continue
}
if err := sys.Set(path, container.Cgroups); err != nil {
return err
}
}
return nil
}
// Freeze toggles the container's freezer cgroup depending on the state
// provided
func (m *Manager) Freeze(state configs.FreezerState) error {
d, err := getCgroupData(m.Cgroups, 0)
if err != nil {
return err
}
dir, err := d.path("freezer")
if err != nil {
return err
}
prevState := m.Cgroups.Freezer
m.Cgroups.Freezer = state
freezer := subsystems["freezer"]
err = freezer.Set(dir, m.Cgroups)
if err != nil {
m.Cgroups.Freezer = prevState
return err
}
return nil
}
func (m *Manager) GetPids() ([]int, error) {
d, err := getCgroupData(m.Cgroups, 0)
if err != nil {
return nil, err
}
dir, err := d.path("devices")
if err != nil {
return nil, err
}
return cgroups.ReadProcsFile(dir)
}
func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
root, err := getCgroupRoot()
if err != nil {
return nil, err
}
cgroup := c.Name
if c.Parent != "" {
cgroup = filepath.Join(c.Parent, cgroup)
}
return &data{
root: root,
cgroup: cgroup,
c: c,
pid: pid,
}, nil
}
func (raw *data) parent(subsystem, mountpoint string) (string, error) {
initPath, err := cgroups.GetInitCgroupDir(subsystem)
if err != nil {
return "", err
}
return filepath.Join(mountpoint, initPath), nil
}
func (raw *data) path(subsystem string) (string, error) {
mnt, err := cgroups.FindCgroupMountpoint(subsystem)
// If we didn't mount the subsystem, there is no point we make the path.
if err != nil {
return "", err
}
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
if filepath.IsAbs(raw.cgroup) {
return filepath.Join(raw.root, subsystem, raw.cgroup), nil
}
parent, err := raw.parent(subsystem, mnt)
if err != nil {
return "", err
}
return filepath.Join(parent, raw.cgroup), nil
}
func (raw *data) join(subsystem string) (string, error) {
path, err := raw.path(subsystem)
if err != nil {
return "", err
}
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
return "", err
}
if err := writeFile(path, CgroupProcesses, strconv.Itoa(raw.pid)); err != nil {
return "", err
}
return path, nil
}
func writeFile(dir, file, data string) error {
// Normally dir should not be empty, one case is that cgroup subsystem
// is not mounted, we will get empty dir, and we want it fail here.
if dir == "" {
return fmt.Errorf("no such directory for %s.", file)
}
return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
}
func readFile(dir, file string) (string, error) {
data, err := ioutil.ReadFile(filepath.Join(dir, file))
return string(data), err
}
func removePath(p string, err error) error {
if err != nil {
return err
}
if p != "" {
return os.RemoveAll(p)
}
return nil
}
func CheckCpushares(path string, c int64) error {
var cpuShares int64
if c == 0 {
return nil
}
fd, err := os.Open(filepath.Join(path, "cpu.shares"))
if err != nil {
return err
}
defer fd.Close()
_, err = fmt.Fscanf(fd, "%d", &cpuShares)
if err != nil && err != io.EOF {
return err
}
if c > cpuShares {
return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares)
} else if c < cpuShares {
return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares)
}
return nil
}