Merge pull request #330 from avagin/api-cgroups

new-api: implement fs and systemd cgroup managers
This commit is contained in:
Victor Marmol 2015-01-14 14:50:14 -08:00
commit c1a4b31593
12 changed files with 213 additions and 119 deletions

View File

@ -1,30 +0,0 @@
package libcontainer
import (
"github.com/docker/libcontainer/cgroups"
)
// TODO(vmarmol): Move this to cgroups and rename to Manager.
type CgroupManager interface {
GetPids() ([]int, error)
GetStats() (*cgroups.Stats, error)
}
func NewCgroupManager() CgroupManager {
return &fsManager{}
}
type fsManager struct {
}
func (m *fsManager) GetPids() ([]int, error) {
// TODO(vmarmol): Implement
//return fs.GetPids(config)
panic("not implemented")
}
func (m *fsManager) GetStats() (*cgroups.Stats, error) {
// TODO(vmarmol): Implement
//return fs.GetStats(config)
panic("not implemented")
}

View File

@ -6,6 +6,33 @@ import (
"github.com/docker/libcontainer/devices"
)
type Manager interface {
// Apply cgroup configuration to the process with the specified pid
Apply(pid int) error
// Returns the PIDs inside the cgroup set
GetPids() ([]int, error)
// Returns statistics for the cgroup set
GetStats() (*Stats, error)
// Toggles the freezer cgroup according with specified state
Freeze(state FreezerState) error
// Destroys the cgroup set
Destroy() error
// NewCgroupManager() and LoadCgroupManager() require following attributes:
// Paths map[string]string
// Cgroups *cgroups.Cgroup
// Paths maps cgroup subsystem to path at which it is mounted.
// Cgroups specifies specific cgroup settings for the various subsystems
// Returns cgroup paths to save in a state file and to be able to
// restore the object later.
GetPaths() map[string]string
}
type FreezerState string
const (

View File

@ -24,6 +24,11 @@ var (
CgroupProcesses = "cgroup.procs"
)
type Manager struct {
Cgroups *cgroups.Cgroup
Paths map[string]string
}
// The absolute path to the root of the cgroup hierarchies.
var cgroupRoot string
@ -57,10 +62,14 @@ type data struct {
pid int
}
func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
d, err := getCgroupData(c, pid)
func (m *Manager) Apply(pid int) error {
if m.Cgroups == nil {
return nil
}
d, err := getCgroupData(m.Cgroups, pid)
if err != nil {
return nil, err
return err
}
paths := make(map[string]string)
@ -71,7 +80,7 @@ func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
}()
for name, sys := range subsystems {
if err := sys.Set(d); err != nil {
return nil, err
return err
}
// FIXME: 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
@ -81,11 +90,21 @@ func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
if cgroups.IsNotFound(err) {
continue
}
return nil, err
return err
}
paths[name] = p
}
return paths, nil
m.Paths = paths
return nil
}
func (m *Manager) Destroy() error {
return cgroups.RemovePaths(m.Paths)
}
func (m *Manager) GetPaths() map[string]string {
return m.Paths
}
// Symmetrical public function to update device based cgroups. Also available
@ -101,9 +120,9 @@ func ApplyDevices(c *cgroups.Cgroup, pid int) error {
return devices.Set(d)
}
func GetStats(systemPaths map[string]string) (*cgroups.Stats, error) {
func (m *Manager) GetStats() (*cgroups.Stats, error) {
stats := cgroups.NewStats()
for name, path := range systemPaths {
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok {
continue
@ -118,21 +137,25 @@ func GetStats(systemPaths map[string]string) (*cgroups.Stats, error) {
// Freeze toggles the container's freezer cgroup depending on the state
// provided
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
d, err := getCgroupData(c, 0)
func (m *Manager) Freeze(state cgroups.FreezerState) error {
d, err := getCgroupData(m.Cgroups, 0)
if err != nil {
return err
}
c.Freezer = state
freezer := subsystems["freezer"]
err = freezer.Set(d)
if err != nil {
return err
}
return freezer.Set(d)
m.Cgroups.Freezer = state
return nil
}
func GetPids(c *cgroups.Cgroup) ([]int, error) {
d, err := getCgroupData(c, 0)
func (m *Manager) GetPids() ([]int, error) {
d, err := getCgroupData(m.Cgroups, 0)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,38 @@
package manager
import (
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
)
// Create a new cgroup manager with specified configuration
// TODO this object is not really initialized until Apply() is called.
// Maybe make this to the equivalent of Apply() at some point?
// @vmarmol
func NewCgroupManager(cgroups *cgroups.Cgroup) cgroups.Manager {
if systemd.UseSystemd() {
return &systemd.Manager{
Cgroups: cgroups,
}
}
return &fs.Manager{
Cgroups: cgroups,
}
}
// Restore a cgroup manager with specified configuration and state
func LoadCgroupManager(cgroups *cgroups.Cgroup, paths map[string]string) cgroups.Manager {
if systemd.UseSystemd() {
return &systemd.Manager{
Cgroups: cgroups,
Paths: paths,
}
}
return &fs.Manager{
Cgroups: cgroups,
Paths: paths,
}
}

View File

@ -8,18 +8,39 @@ import (
"github.com/docker/libcontainer/cgroups"
)
type Manager struct {
Cgroups *cgroups.Cgroup
Paths map[string]string
}
func UseSystemd() bool {
return false
}
func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
func (m *Manager) Apply(pid int) error {
return fmt.Errorf("Systemd not supported")
}
func (m *Manager) GetPids() ([]int, error) {
return nil, fmt.Errorf("Systemd not supported")
}
func GetPids(c *cgroups.Cgroup) ([]int, error) {
func (m *Manager) Destroy() error {
return fmt.Errorf("Systemd not supported")
}
func (m *Manager) GetPaths() map[string]string {
return nil
}
func (m *Manager) GetStats() (*cgroups.Stats, error) {
return nil, fmt.Errorf("Systemd not supported")
}
func (m *Manager) Freeze(state cgroups.FreezerState) error {
return fmt.Errorf("Systemd not supported")
}
func ApplyDevices(c *cgroups.Cgroup, pid int) error {
return fmt.Errorf("Systemd not supported")
}

View File

@ -19,8 +19,9 @@ import (
"github.com/godbus/dbus"
)
type systemdCgroup struct {
cgroup *cgroups.Cgroup
type Manager struct {
Cgroups *cgroups.Cgroup
Paths map[string]string
}
type subsystem interface {
@ -81,16 +82,14 @@ func getIfaceForUnit(unitName string) string {
return "Unit"
}
func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
func (m *Manager) Apply(pid int) error {
var (
c = m.Cgroups
unitName = getUnitName(c)
slice = "system.slice"
properties []systemd.Property
res = &systemdCgroup{}
)
res.cgroup = c
if c.Slice != "" {
slice = c.Slice
}
@ -120,19 +119,19 @@ func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
}
if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil {
return nil, err
return err
}
if !c.AllowAllDevices {
if err := joinDevices(c, pid); err != nil {
return nil, err
return err
}
}
// -1 disables memorySwap
if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
if err := joinMemory(c, pid); err != nil {
return nil, err
return err
}
}
@ -140,11 +139,11 @@ func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
// we need to manually join the freezer and cpuset cgroup in systemd
// because it does not currently support it via the dbus api.
if err := joinFreezer(c, pid); err != nil {
return nil, err
return err
}
if err := joinCpuset(c, pid); err != nil {
return nil, err
return err
}
paths := make(map[string]string)
@ -158,17 +157,28 @@ func Apply(c *cgroups.Cgroup, pid int) (map[string]string, error) {
"perf_event",
"freezer",
} {
subsystemPath, err := getSubsystemPath(res.cgroup, sysname)
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if cgroups.IsNotFound(err) {
continue
}
return nil, err
return err
}
paths[sysname] = subsystemPath
}
return paths, nil
m.Paths = paths
return nil
}
func (m *Manager) Destroy() error {
return cgroups.RemovePaths(m.Paths)
}
func (m *Manager) GetPaths() map[string]string {
return m.Paths
}
func writeFile(dir, file, data string) error {
@ -207,8 +217,8 @@ func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
path, err := getSubsystemPath(c, "freezer")
func (m *Manager) Freeze(state cgroups.FreezerState) error {
path, err := getSubsystemPath(m.Cgroups, "freezer")
if err != nil {
return err
}
@ -229,8 +239,8 @@ func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
return nil
}
func GetPids(c *cgroups.Cgroup) ([]int, error) {
path, err := getSubsystemPath(c, "cpu")
func (m *Manager) GetPids() ([]int, error) {
path, err := getSubsystemPath(m.Cgroups, "cpu")
if err != nil {
return nil, err
}
@ -238,6 +248,10 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
return cgroups.ReadProcsFile(path)
}
func (m *Manager) GetStats() (*cgroups.Stats, error) {
panic("not implemented")
}
func getUnitName(c *cgroups.Cgroup) string {
return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
}

View File

@ -10,6 +10,7 @@ import (
"path/filepath"
"syscall"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/namespaces"
"github.com/docker/libcontainer/network"
@ -21,7 +22,7 @@ type linuxContainer struct {
root string
config *configs.Config
state *configs.State
cgroupManager CgroupManager
cgroupManager cgroups.Manager
initArgs []string
}
@ -133,7 +134,7 @@ func (c *linuxContainer) updateStateFile() error {
}
func (c *linuxContainer) startInitProcess(cmd *exec.Cmd, config *ProcessConfig) error {
err := namespaces.Exec(config.Args, config.Env, cmd, c.config, c.state)
err := namespaces.Exec(config.Args, config.Env, cmd, c.config, c.cgroupManager, c.state)
if err != nil {
return err
}

View File

@ -22,6 +22,22 @@ func (m *mockCgroupManager) GetStats() (*cgroups.Stats, error) {
return m.stats, nil
}
func (m *mockCgroupManager) Apply(pid int) error {
return nil
}
func (m *mockCgroupManager) Destroy() error {
return nil
}
func (m *mockCgroupManager) GetPaths() map[string]string {
return nil
}
func (m *mockCgroupManager) Freeze(state cgroups.FreezerState) error {
return nil
}
func TestGetContainerPids(t *testing.T) {
container := &linuxContainer{
id: "myid",

View File

@ -11,6 +11,7 @@ import (
"github.com/golang/glog"
cgroups "github.com/docker/libcontainer/cgroups/manager"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/namespaces"
)
@ -88,7 +89,7 @@ func (l *linuxFactory) Create(id string, config *configs.Config) (Container, err
return nil, newGenericError(err, SystemError)
}
cgroupManager := NewCgroupManager()
cgroupManager := cgroups.NewCgroupManager(config.Cgroups)
return &linuxContainer{
id: id,
root: containerRoot,
@ -116,7 +117,7 @@ func (l *linuxFactory) Load(id string) (Container, error) {
return nil, err
}
cgroupManager := NewCgroupManager()
cgroupManager := cgroups.LoadCgroupManager(config.Cgroups, state.CgroupPaths)
glog.Infof("using %s as cgroup manager", cgroupManager)
return &linuxContainer{
id: id,

View File

@ -10,8 +10,6 @@ import (
"syscall"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/network"
"github.com/docker/libcontainer/system"
@ -21,7 +19,7 @@ import (
// Move this to libcontainer package.
// Exec performs setup outside of a namespace so that a container can be
// executed. Exec is a high level function for working with container namespaces.
func Exec(args []string, env []string, command *exec.Cmd, container *configs.Config, state *configs.State) error {
func Exec(args []string, env []string, command *exec.Cmd, container *configs.Config, cgroupManager cgroups.Manager, state *configs.State) error {
var err error
// create a pipe so that we can syncronize with the namespaced process and
@ -70,11 +68,11 @@ func Exec(args []string, env []string, command *exec.Cmd, container *configs.Con
// Do this before syncing with child so that no children
// can escape the cgroup
cgroupPaths, err := SetupCgroups(container, command.Process.Pid)
err = cgroupManager.Apply(command.Process.Pid)
if err != nil {
return terminate(err)
}
defer cgroups.RemovePaths(cgroupPaths)
defer cgroupManager.Destroy()
var networkState network.NetworkState
if err := InitializeNetworking(container, command.Process.Pid, &networkState); err != nil {
@ -102,7 +100,7 @@ func Exec(args []string, env []string, command *exec.Cmd, container *configs.Con
state.InitPid = command.Process.Pid
state.InitStartTime = started
state.NetworkState = networkState
state.CgroupPaths = cgroupPaths
state.CgroupPaths = cgroupManager.GetPaths()
return nil
}
@ -140,19 +138,6 @@ func DefaultCreateCommand(container *configs.Config, console, dataPath, init str
return command
}
// SetupCgroups applies the cgroup restrictions to the process running in the container based
// on the container's configuration
func SetupCgroups(container *configs.Config, nspid int) (map[string]string, error) {
if container.Cgroups != nil {
c := container.Cgroups
if systemd.UseSystemd() {
return systemd.Apply(c, nspid)
}
return fs.Apply(c, nspid)
}
return map[string]string{}, nil
}
// InitializeNetworking creates the container's network stack outside of the namespace and moves
// interfaces into the container's net namespaces if necessary
func InitializeNetworking(container *configs.Config, nspid int, networkState *network.NetworkState) error {

View File

@ -28,17 +28,7 @@ var execCommand = cli.Command{
},
}
func execAction(context *cli.Context) {
var exitCode int
process := &libcontainer.ProcessConfig{
Args: context.Args(),
Env: context.StringSlice("env"),
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
func getContainer(context *cli.Context) (libcontainer.Container, error) {
factory, err := libcontainer.New(context.GlobalString("root"), []string{os.Args[0], "init", "--fd", "3", "--"})
if err != nil {
log.Fatal(err)
@ -55,6 +45,22 @@ func execAction(context *cli.Context) {
}
container, err = factory.Create(id, config)
}
return container, err
}
func execAction(context *cli.Context) {
var exitCode int
process := &libcontainer.ProcessConfig{
Args: context.Args(),
Env: context.StringSlice("env"),
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
container, err := getContainer(context)
if err != nil {
log.Fatal(err)
}

View File

@ -4,9 +4,6 @@ import (
"log"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
)
var pauseCommand = cli.Command{
@ -22,28 +19,23 @@ var unpauseCommand = cli.Command{
}
func pauseAction(context *cli.Context) {
if err := toggle(cgroups.Frozen); err != nil {
container, err := getContainer(context)
if err != nil {
log.Fatal(err)
}
if err = container.Pause(); err != nil {
log.Fatal(err)
}
}
func unpauseAction(context *cli.Context) {
if err := toggle(cgroups.Thawed); err != nil {
container, err := getContainer(context)
if err != nil {
log.Fatal(err)
}
if err = container.Resume(); err != nil {
log.Fatal(err)
}
}
func toggle(state cgroups.FreezerState) error {
container, err := loadConfig()
if err != nil {
return err
}
if systemd.UseSystemd() {
err = systemd.Freeze(container.Cgroups, state)
} else {
err = fs.Freeze(container.Cgroups, state)
}
return err
}