diff --git a/configs/cgroup.go b/configs/cgroup.go index 0dffc640..92b9286a 100644 --- a/configs/cgroup.go +++ b/configs/cgroup.go @@ -9,46 +9,46 @@ const ( ) type Cgroup struct { - Name string `json:"name,omitempty"` + Name string `json:"name"` // name of parent cgroup or slice - Parent string `json:"parent,omitempty"` + Parent string `json:"parent"` // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. - AllowAllDevices bool `json:"allow_all_devices,omitempty"` + AllowAllDevices bool `json:"allow_all_devices"` - AllowedDevices []*Device `json:"allowed_devices,omitempty"` + AllowedDevices []*Device `json:"allowed_devices"` // Memory limit (in bytes) - Memory int64 `json:"memory,omitempty"` + Memory int64 `json:"memory"` // Memory reservation or soft_limit (in bytes) - MemoryReservation int64 `json:"memory_reservation,omitempty"` + MemoryReservation int64 `json:"memory_reservation"` // Total memory usage (memory + swap); set `-1' to disable swap - MemorySwap int64 `json:"memory_swap,omitempty"` + MemorySwap int64 `json:"memory_swap"` // CPU shares (relative weight vs. other containers) - CpuShares int64 `json:"cpu_shares,omitempty"` + CpuShares int64 `json:"cpu_shares"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. - CpuQuota int64 `json:"cpu_quota,omitempty"` + CpuQuota int64 `json:"cpu_quota"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. - CpuPeriod int64 `json:"cpu_period,omitempty"` + CpuPeriod int64 `json:"cpu_period"` // CPU to use - CpusetCpus string `json:"cpuset_cpus,omitempty"` + CpusetCpus string `json:"cpuset_cpus"` // MEM to use - CpusetMems string `json:"cpuset_mems,omitempty"` + CpusetMems string `json:"cpuset_mems"` // Specifies per cgroup weight, range is from 10 to 1000. - BlkioWeight int64 `json:"blkio_weight,omitempty"` + BlkioWeight int64 `json:"blkio_weight"` // set the freeze value for the process - Freezer FreezerState `json:"freezer,omitempty"` + Freezer FreezerState `json:"freezer"` // Parent slice to use for systemd TODO: remove in favor or parent - Slice string `json:"slice,omitempty"` + Slice string `json:"slice"` } diff --git a/configs/config.go b/configs/config.go index d8e2c9eb..fc7450d9 100644 --- a/configs/config.go +++ b/configs/config.go @@ -3,98 +3,98 @@ package configs import "fmt" type Rlimit struct { - Type int `json:"type,omitempty"` - Hard uint64 `json:"hard,omitempty"` - Soft uint64 `json:"soft,omitempty"` + Type int `json:"type"` + Hard uint64 `json:"hard"` + Soft uint64 `json:"soft"` } // IDMap represents UID/GID Mappings for User Namespaces. type IDMap struct { - ContainerID int `json:"container_id,omitempty"` - HostID int `json:"host_id,omitempty"` - Size int `json:"size,omitempty"` + ContainerID int `json:"container_id"` + HostID int `json:"host_id"` + Size int `json:"size"` } // Config defines configuration options for executing a process inside a contained environment. type Config struct { // NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs // This is a common option when the container is running in ramdisk - NoPivotRoot bool `json:"no_pivot_root,omitempty"` + NoPivotRoot bool `json:"no_pivot_root"` // ParentDeathSignal specifies the signal that is sent to the container's process in the case // that the parent process dies. - ParentDeathSignal int `json:"parent_death_signal,omitempty"` + ParentDeathSignal int `json:"parent_death_signal"` // PivotDir allows a custom directory inside the container's root filesystem to be used as pivot, when NoPivotRoot is not set. // When a custom PivotDir not set, a temporary dir inside the root filesystem will be used. The pivot dir needs to be writeable. // This is required when using read only root filesystems. In these cases, a read/writeable path can be (bind) mounted somewhere inside the root filesystem to act as pivot. - PivotDir string `json:"pivot_dir,omitempty"` + PivotDir string `json:"pivot_dir"` // Path to a directory containing the container's root filesystem. - Rootfs string `json:"rootfs,omitempty"` + Rootfs string `json:"rootfs"` // Readonlyfs will remount the container's rootfs as readonly where only externally mounted // bind mounts are writtable. - Readonlyfs bool `json:"readonlyfs,omitempty"` + Readonlyfs bool `json:"readonlyfs"` // Mounts specify additional source and destination paths that will be mounted inside the container's // rootfs and mount namespace if specified - Mounts []*Mount `json:"mounts,omitempty"` + Mounts []*Mount `json:"mounts"` // The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well! - Devices []*Device `json:"devices,omitempty"` + Devices []*Device `json:"devices"` - MountLabel string `json:"mount_label,omitempty"` + MountLabel string `json:"mount_label"` // Hostname optionally sets the container's hostname if provided - Hostname string `json:"hostname,omitempty"` + Hostname string `json:"hostname"` // Console is the path to the console allocated to the container. - Console string `json:"console,omitempty"` + Console string `json:"console"` // Namespaces specifies the container's namespaces that it should setup when cloning the init process // If a namespace is not provided that namespace is shared from the container's parent process - Namespaces Namespaces `json:"namespaces,omitempty"` + Namespaces Namespaces `json:"namespaces"` // Capabilities specify the capabilities to keep when executing the process inside the container // All capbilities not specified will be dropped from the processes capability mask - Capabilities []string `json:"capabilities,omitempty"` + Capabilities []string `json:"capabilities"` // Networks specifies the container's network setup to be created - Networks []*Network `json:"networks,omitempty"` + Networks []*Network `json:"networks"` // Routes can be specified to create entries in the route table as the container is started - Routes []*Route `json:"routes,omitempty"` + Routes []*Route `json:"routes"` // Cgroups specifies specific cgroup settings for the various subsystems that the container is // placed into to limit the resources the container has available - Cgroups *Cgroup `json:"cgroups,omitempty"` + Cgroups *Cgroup `json:"cgroups"` // AppArmorProfile specifies the profile to apply to the process running in the container and is // change at the time the process is execed - AppArmorProfile string `json:"apparmor_profile,omitempty"` + AppArmorProfile string `json:"apparmor_profile"` // ProcessLabel specifies the label to apply to the process running in the container. It is // commonly used by selinux - ProcessLabel string `json:"process_label,omitempty"` + ProcessLabel string `json:"process_label"` // RestrictSys will remount /proc/sys, /sys, and mask over sysrq-trigger as well as /proc/irq and // /proc/bus - RestrictSys bool `json:"restrict_sys,omitempty"` + RestrictSys bool `json:"restrict_sys"` // Rlimits specifies the resource limits, such as max open files, to set in the container // If Rlimits are not set, the container will inherit rlimits from the parent process - Rlimits []Rlimit `json:"rlimits,omitempty"` + Rlimits []Rlimit `json:"rlimits"` // AdditionalGroups specifies the gids that should be added to supplementary groups // in addition to those that the user belongs to. - AdditionalGroups []int `json:"additional_groups,omitempty"` + AdditionalGroups []int `json:"additional_groups"` // UidMappings is an array of User ID mappings for User Namespaces - UidMappings []IDMap `json:"uid_mappings,omitempty"` + UidMappings []IDMap `json:"uid_mappings"` // GidMappings is an array of Group ID mappings for User Namespaces - GidMappings []IDMap `json:"gid_mappings,omitempty"` + GidMappings []IDMap `json:"gid_mappings"` } // Gets the root uid for the process on host which could be non-zero diff --git a/configs/device.go b/configs/device.go index a8117068..abff2669 100644 --- a/configs/device.go +++ b/configs/device.go @@ -11,28 +11,28 @@ const ( type Device struct { // Device type, block, char, etc. - Type rune `json:"type,omitempty"` + Type rune `json:"type"` // Path to the device. - Path string `json:"path,omitempty"` + Path string `json:"path"` // Major is the device's major number. - Major int64 `json:"major,omitempty"` + Major int64 `json:"major"` // Minor is the device's minor number. - Minor int64 `json:"minor,omitempty"` + Minor int64 `json:"minor"` // Cgroup permissions format, rwm. - Permissions string `json:"permissions,omitempty"` + Permissions string `json:"permissions"` // FileMode permission bits for the device. - FileMode os.FileMode `json:"file_mode,omitempty"` + FileMode os.FileMode `json:"file_mode"` // Uid of the device. - Uid uint32 `json:"uid,omitempty"` + Uid uint32 `json:"uid"` // Gid of the device. - Gid uint32 `json:"gid,omitempty"` + Gid uint32 `json:"gid"` } func (d *Device) CgroupString() string { diff --git a/configs/mount.go b/configs/mount.go index 2f20de9e..eb26c5c0 100644 --- a/configs/mount.go +++ b/configs/mount.go @@ -1,11 +1,11 @@ package configs type Mount struct { - Type string `json:"type,omitempty"` - Source string `json:"source,omitempty"` // Source path, in the host namespace - Destination string `json:"destination,omitempty"` // Destination path, in the container - Writable bool `json:"writable,omitempty"` - Relabel string `json:"relabel,omitempty"` // Relabel source if set, "z" indicates shared, "Z" indicates unshared - Private bool `json:"private,omitempty"` - Slave bool `json:"slave,omitempty"` + Type string `json:"type"` + Source string `json:"source"` // Source path, in the host namespace + Destination string `json:"destination"` // Destination path, in the container + Writable bool `json:"writable"` + Relabel string `json:"relabel"` // Relabel source if set, "z" indicates shared, "Z" indicates unshared + Private bool `json:"private"` + Slave bool `json:"slave"` } diff --git a/configs/namespaces.go b/configs/namespaces.go index a227f1ba..9078e6ab 100644 --- a/configs/namespaces.go +++ b/configs/namespaces.go @@ -1,6 +1,7 @@ package configs import ( + "fmt" "syscall" ) @@ -19,13 +20,39 @@ const ( // alternate path that is able to be joined via setns. type Namespace struct { Type NamespaceType `json:"type"` - Path string `json:"path,omitempty"` + Path string `json:"path"` } func (n *Namespace) Syscall() int { return namespaceInfo[n.Type] } +func (n *Namespace) GetPath(pid int) string { + if n.Path != "" { + return n.Path + } + return fmt.Sprintf("/proc/%d/ns/%s", pid, n.file()) +} + +func (n *Namespace) file() string { + file := "" + switch n.Type { + case NEWNET: + file = "net" + case NEWNS: + file = "mnt" + case NEWPID: + file = "pid" + case NEWIPC: + file = "ipc" + case NEWUSER: + file = "user" + case NEWUTS: + file = "uts" + } + return file +} + type Namespaces []Namespace func (n *Namespaces) Remove(t NamespaceType) bool { diff --git a/configs/network.go b/configs/network.go index 890953a0..55443988 100644 --- a/configs/network.go +++ b/configs/network.go @@ -6,42 +6,42 @@ package configs // container to be setup with the host's networking stack type Network struct { // Type sets the networks type, commonly veth and loopback - Type string `json:"type,omitempty"` + Type string `json:"type"` // Name of the network interface - Name string `json:"name,omitempty"` + Name string `json:"name"` // The bridge to use. - Bridge string `json:"bridge,omitempty"` + Bridge string `json:"bridge"` // MacAddress contains the MAC address to set on the network interface - MacAddress string `json:"mac_address,omitempty"` + MacAddress string `json:"mac_address"` // Address contains the IPv4 and mask to set on the network interface - Address string `json:"address,omitempty"` + Address string `json:"address"` // Gateway sets the gateway address that is used as the default for the interface - Gateway string `json:"gateway,omitempty"` + Gateway string `json:"gateway"` // IPv6Address contains the IPv6 and mask to set on the network interface - IPv6Address string `json:"ipv6_address,omitempty"` + IPv6Address string `json:"ipv6_address"` // IPv6Gateway sets the ipv6 gateway address that is used as the default for the interface - IPv6Gateway string `json:"ipv6_gateway,omitempty"` + IPv6Gateway string `json:"ipv6_gateway"` // Mtu sets the mtu value for the interface and will be mirrored on both the host and // container's interfaces if a pair is created, specifically in the case of type veth // Note: This does not apply to loopback interfaces. - Mtu int `json:"mtu,omitempty"` + Mtu int `json:"mtu"` // TxQueueLen sets the tx_queuelen value for the interface and will be mirrored on both the host and // container's interfaces if a pair is created, specifically in the case of type veth // Note: This does not apply to loopback interfaces. - TxQueueLen int `json:"txqueuelen,omitempty"` + TxQueueLen int `json:"txqueuelen"` // HostInterfaceName is a unique name of a veth pair that resides on in the host interface of the // container. - HostInterfaceName string `json:"host_interface_name,omitempty"` + HostInterfaceName string `json:"host_interface_name"` } // Routes can be specified to create entries in the route table as the container is started @@ -53,14 +53,14 @@ type Network struct { // destination of 0.0.0.0(or *) when viewed in the route table. type Route struct { // Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6 - Destination string `json:"destination,omitempty"` + Destination string `json:"destination"` // Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6 - Source string `json:"source,omitempty"` + Source string `json:"source"` // Sets the gateway. Accepts IPv4 and IPv6 - Gateway string `json:"gateway,omitempty"` + Gateway string `json:"gateway"` // The device to set this route up for, for example: eth0 - InterfaceName string `json:"interface_name,omitempty"` + InterfaceName string `json:"interface_name"` } diff --git a/configs/state.go b/configs/state.go deleted file mode 100644 index c1e8adcf..00000000 --- a/configs/state.go +++ /dev/null @@ -1,30 +0,0 @@ -package configs - -// The status of a container. -type Status int - -const ( - // The container exists and is running. - Running Status = iota + 1 - - // The container exists, it is in the process of being paused. - Pausing - - // The container exists, but all its processes are paused. - Paused - - // The container does not exist. - Destroyed -) - -// State represents a running container's state -type State struct { - // InitPid is the init process id in the parent namespace - InitPid int `json:"init_pid,omitempty"` - - // InitStartTime is the init process start time - InitStartTime string `json:"init_start_time,omitempty"` - - // Path to all the cgroups setup for a container. Key is cgroup subsystem name. - CgroupPaths map[string]string `json:"cgroup_paths,omitempty"` -} diff --git a/console.go b/console.go index d3392ee3..042a2a2e 100644 --- a/console.go +++ b/console.go @@ -2,7 +2,7 @@ package libcontainer import "io" -// Console is a psuedo TTY. +// Console represents a pseudo TTY. type Console interface { io.ReadWriter io.Closer diff --git a/container.go b/container.go index 7c9a3308..2d4cbcad 100644 --- a/container.go +++ b/container.go @@ -1,6 +1,7 @@ -/* -NOTE: The API is in flux and mainly not implemented. Proceed with caution until further notice. -*/ +// Libcontainer provides a native Go implementation for creating containers +// with namespaces, cgroups, capabilities, and filesystem access controls. +// It allows you to manage the lifecycle of the container performing additional operations +// after the container is created. package libcontainer import ( @@ -9,6 +10,46 @@ import ( "github.com/docker/libcontainer/configs" ) +// The status of a container. +type Status int + +const ( + // The container exists and is running. + Running Status = iota + 1 + + // The container exists, it is in the process of being paused. + Pausing + + // The container exists, but all its processes are paused. + Paused + + // The container does not exist. + Destroyed +) + +// State represents a running container's state +type State struct { + // ID is the container ID. + ID string `json:"id"` + + // InitProcessPid is the init process id in the parent namespace. + InitProcessPid int `json:"init_process_pid"` + + // InitProcessStartTime is the init process start time. + InitProcessStartTime string `json:"init_process_start"` + + // Path to all the cgroups setup for a container. Key is cgroup subsystem name + // with the value as the path. + CgroupPaths map[string]string `json:"cgroup_paths"` + + // NamespacePaths are filepaths to the container's namespaces. Key is the namespace name + // with the value as the path. + NamespacePaths map[string]string `json:"namespace_paths"` + + // Config is the container's configuration. + Config configs.Config `json:"config"` +} + // A libcontainer container object. // // Each container is thread-safe within the same process. Since a container can @@ -21,8 +62,15 @@ type Container interface { // Returns the current status of the container. // // errors: + // ContainerDestroyed - Container no longer exists, // Systemerror - System error. - Status() (configs.Status, error) + Status() (Status, error) + + // State returns the current container's state information. + // + // errors: + // Systemerror - System erroor. + State() (*State, error) // Returns the current config of the container. Config() configs.Config @@ -89,9 +137,9 @@ type Container interface { // Systemerror - System error. Signal(signal os.Signal) error - // OOM returns a read-only channel signaling when the container receives an OOM notification. + // NotifyOOM returns a read-only channel signaling when the container receives an OOM notification. // // errors: // Systemerror - System error. - OOM() (<-chan struct{}, error) + NotifyOOM() (<-chan struct{}, error) } diff --git a/error.go b/error.go index 31ebb320..85b0dcaf 100644 --- a/error.go +++ b/error.go @@ -15,6 +15,7 @@ const ( ContainerNotExists ContainerPaused ContainerNotStopped + ContainerNotRunning // Common errors ConfigInvalid @@ -36,7 +37,9 @@ func (c ErrorCode) String() string { case ContainerNotExists: return "Container does not exist" case ContainerNotStopped: - return "Container isn't stopped" + return "Container is not stopped" + case ContainerNotRunning: + return "Container is not running" default: return "Unknown error" } diff --git a/generic_error.go b/generic_error.go index 08a47f61..ff614ee6 100644 --- a/generic_error.go +++ b/generic_error.go @@ -11,38 +11,55 @@ import ( var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} Code: {{.ECode}} +{{if .Err }} Message: {{.Err.Error}} +{{end}} Frames:{{range $i, $frame := .Stack.Frames}} --- {{$i}}: {{$frame.Function}} Package: {{$frame.Package}} -File: {{$frame.File}}{{end}} +File: {{$frame.File}}@{{$frame.Line}}{{end}} `)) func newGenericError(err error, c ErrorCode) Error { - return &GenericError{ + if le, ok := err.(Error); ok { + return le + } + return &genericError{ Timestamp: time.Now(), Err: err, ECode: c, - Stack: stacktrace.Capture(2), + Stack: stacktrace.Capture(1), } } -type GenericError struct { +func newSystemError(err error) Error { + if le, ok := err.(Error); ok { + return le + } + return &genericError{ + Timestamp: time.Now(), + Err: err, + ECode: SystemError, + Stack: stacktrace.Capture(1), + } +} + +type genericError struct { Timestamp time.Time ECode ErrorCode Err error Stack stacktrace.Stacktrace } -func (e *GenericError) Error() string { +func (e *genericError) Error() string { return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Err) } -func (e *GenericError) Code() ErrorCode { +func (e *genericError) Code() ErrorCode { return e.ECode } -func (e *GenericError) Detail(w io.Writer) error { +func (e *genericError) Detail(w io.Writer) error { return errorTemplate.Execute(w, e) } diff --git a/integration/exec_test.go b/integration/exec_test.go index 5e9fa0e4..4fef06ea 100644 --- a/integration/exec_test.go +++ b/integration/exec_test.go @@ -383,7 +383,7 @@ func TestFreeze(t *testing.T) { if err := container.Resume(); err != nil { t.Fatal(err) } - if state != configs.Paused { + if state != libcontainer.Paused { t.Fatal("Unexpected state: ", state) } diff --git a/linux_console.go b/linux_console.go index f1eeaedf..5cb5f713 100644 --- a/linux_console.go +++ b/linux_console.go @@ -12,6 +12,8 @@ import ( "github.com/docker/libcontainer/label" ) +// NewConsole returns an initalized console that can be used within a container by copying bytes +// from the master side to the slave that is attached as the tty for the container's init process. func NewConsole() (Console, error) { master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) if err != nil { diff --git a/linux_container.go b/linux_container.go index c92ffa71..492936ea 100644 --- a/linux_container.go +++ b/linux_container.go @@ -3,9 +3,11 @@ package libcontainer import ( + "encoding/json" "fmt" "os" "os/exec" + "path/filepath" "syscall" "github.com/docker/libcontainer/cgroups" @@ -32,29 +34,54 @@ func (c *linuxContainer) Config() configs.Config { return *c.config } -func (c *linuxContainer) Status() (configs.Status, error) { +func (c *linuxContainer) Status() (Status, error) { if c.initProcess == nil { - return configs.Destroyed, nil + return Destroyed, nil } // return Running if the init process is alive if err := syscall.Kill(c.initProcess.pid(), 0); err != nil { if err == syscall.ESRCH { - return configs.Destroyed, nil + return Destroyed, nil } - return 0, err + return 0, newSystemError(err) } - if c.config.Cgroups != nil && - c.config.Cgroups.Freezer == configs.Frozen { - return configs.Paused, nil + if c.config.Cgroups != nil && c.config.Cgroups.Freezer == configs.Frozen { + return Paused, nil } - return configs.Running, nil + return Running, nil +} + +func (c *linuxContainer) State() (*State, error) { + status, err := c.Status() + if err != nil { + return nil, err + } + if status == Destroyed { + return nil, newGenericError(fmt.Errorf("container destroyed"), ContainerNotExists) + } + startTime, err := c.initProcess.startTime() + if err != nil { + return nil, newSystemError(err) + } + state := &State{ + ID: c.ID(), + Config: *c.config, + InitProcessPid: c.initProcess.pid(), + InitProcessStartTime: startTime, + CgroupPaths: c.cgroupManager.GetPaths(), + NamespacePaths: make(map[string]string), + } + for _, ns := range c.config.Namespaces { + state.NamespacePaths[string(ns.Type)] = ns.GetPath(c.initProcess.pid()) + } + return state, nil } func (c *linuxContainer) Processes() ([]int, error) { glog.Info("fetch container processes") pids, err := c.cgroupManager.GetPids() if err != nil { - return nil, newGenericError(err, SystemError) + return nil, newSystemError(err) } return pids, nil } @@ -66,14 +93,14 @@ func (c *linuxContainer) Stats() (*Stats, error) { stats = &Stats{} ) if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil { - return stats, newGenericError(err, SystemError) + return stats, newSystemError(err) } for _, iface := range c.config.Networks { switch iface.Type { case "veth": istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) if err != nil { - return stats, newGenericError(err, SystemError) + return stats, newSystemError(err) } stats.Interfaces = append(stats.Interfaces, istats) } @@ -86,20 +113,20 @@ func (c *linuxContainer) Start(process *Process) (int, error) { if err != nil { return -1, err } - doInit := status == configs.Destroyed + doInit := status == Destroyed parent, err := c.newParentProcess(process, doInit) if err != nil { - return -1, err + return -1, newSystemError(err) } if err := parent.start(); err != nil { // terminate the process to ensure that it properly is reaped. if err := parent.terminate(); err != nil { glog.Warning(err) } - return -1, err + return -1, newSystemError(err) } if doInit { - c.initProcess = parent + c.updateState(parent) } return parent.pid(), nil } @@ -107,11 +134,11 @@ func (c *linuxContainer) Start(process *Process) (int, error) { func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { parentPipe, childPipe, err := newPipe() if err != nil { - return nil, err + return nil, newSystemError(err) } cmd, err := c.commandTemplate(p, childPipe) if err != nil { - return nil, err + return nil, newSystemError(err) } if !doInit { return c.newSetnsProcess(p, cmd, parentPipe, childPipe), nil @@ -129,13 +156,15 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. cmd.SysProcAttr = &syscall.SysProcAttr{} } cmd.ExtraFiles = []*os.File{childPipe} - cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) + cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL + if c.config.ParentDeathSignal > 0 { + cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) + } return cmd, nil } func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) *initProcess { t := "_LIBCONTAINER_INITTYPE=standard" - cloneFlags := c.config.Namespaces.CloneFlags() if cloneFlags&syscall.CLONE_NEWUSER != 0 { c.addUidGidMappings(cmd.SysProcAttr) @@ -176,6 +205,8 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig { Config: c.config, Args: process.Args, Env: process.Env, + User: process.User, + Cwd: process.Cwd, } } @@ -212,11 +243,20 @@ func (c *linuxContainer) Destroy() error { if err != nil { return err } - if status != configs.Destroyed { + if status != Destroyed { return newGenericError(nil, ContainerNotStopped) } - // TODO: remove cgroups - return os.RemoveAll(c.root) + if !c.config.Namespaces.Contains(configs.NEWPID) { + if err := killCgroupProcesses(c.cgroupManager); err != nil { + glog.Warning(err) + } + } + err = c.cgroupManager.Destroy() + if rerr := os.RemoveAll(c.root); err == nil { + err = rerr + } + c.initProcess = nil + return err } func (c *linuxContainer) Pause() error { @@ -228,11 +268,26 @@ func (c *linuxContainer) Resume() error { } func (c *linuxContainer) Signal(signal os.Signal) error { - glog.Infof("sending signal %d to pid %d", signal, c.initProcess.pid()) + if c.initProcess == nil { + return newGenericError(nil, ContainerNotRunning) + } return c.initProcess.signal(signal) } -// TODO: rename to be more descriptive -func (c *linuxContainer) OOM() (<-chan struct{}, error) { - return NotifyOnOOM(c.cgroupManager.GetPaths()) +func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) { + return notifyOnOOM(c.cgroupManager.GetPaths()) +} + +func (c *linuxContainer) updateState(process parentProcess) error { + c.initProcess = process + state, err := c.State() + if err != nil { + return err + } + f, err := os.Create(filepath.Join(c.root, stateFilename)) + if err != nil { + return err + } + defer f.Close() + return json.NewEncoder(f).Encode(state) } diff --git a/linux_container_test.go b/linux_container_test.go index 11ad253e..8dbb39c5 100644 --- a/linux_container_test.go +++ b/linux_container_test.go @@ -3,6 +3,8 @@ package libcontainer import ( + "fmt" + "os" "testing" "github.com/docker/libcontainer/cgroups" @@ -12,6 +14,7 @@ import ( type mockCgroupManager struct { pids []int stats *cgroups.Stats + paths map[string]string } func (m *mockCgroupManager) GetPids() ([]int, error) { @@ -31,25 +34,52 @@ func (m *mockCgroupManager) Destroy() error { } func (m *mockCgroupManager) GetPaths() map[string]string { - return nil + return m.paths } func (m *mockCgroupManager) Freeze(state configs.FreezerState) error { return nil } +type mockProcess struct { + _pid int + started string +} + +func (m *mockProcess) terminate() error { + return nil +} + +func (m *mockProcess) pid() int { + return m._pid +} + +func (m *mockProcess) startTime() (string, error) { + return m.started, nil +} + +func (m *mockProcess) start() error { + return nil +} + +func (m *mockProcess) wait() (*os.ProcessState, error) { + return nil, nil +} + +func (m *mockProcess) signal(_ os.Signal) error { + return nil +} + func TestGetContainerPids(t *testing.T) { container := &linuxContainer{ id: "myid", config: &configs.Config{}, cgroupManager: &mockCgroupManager{pids: []int{1, 2, 3}}, } - pids, err := container.Processes() if err != nil { t.Fatal(err) } - for i, expected := range []int{1, 2, 3} { if pids[i] != expected { t.Fatalf("expected pid %d but received %d", expected, pids[i]) @@ -70,7 +100,6 @@ func TestGetContainerStats(t *testing.T) { }, }, } - stats, err := container.Stats() if err != nil { t.Fatal(err) @@ -82,3 +111,86 @@ func TestGetContainerStats(t *testing.T) { t.Fatalf("expected memory usage 1024 but recevied %d", stats.CgroupStats.MemoryStats.Usage) } } + +func TestGetContainerState(t *testing.T) { + var ( + pid = os.Getpid() + expectedMemoryPath = "/sys/fs/cgroup/memory/myid" + expectedNetworkPath = "/networks/fd" + ) + container := &linuxContainer{ + id: "myid", + config: &configs.Config{ + Namespaces: configs.Namespaces{ + {Type: configs.NEWPID}, + {Type: configs.NEWNS}, + {Type: configs.NEWNET, Path: expectedNetworkPath}, + {Type: configs.NEWUTS}, + {Type: configs.NEWIPC}, + }, + }, + initProcess: &mockProcess{ + _pid: pid, + started: "010", + }, + cgroupManager: &mockCgroupManager{ + pids: []int{1, 2, 3}, + stats: &cgroups.Stats{ + MemoryStats: cgroups.MemoryStats{ + Usage: 1024, + }, + }, + paths: map[string]string{ + "memory": expectedMemoryPath, + }, + }, + } + state, err := container.State() + if err != nil { + t.Fatal(err) + } + if state.InitProcessPid != pid { + t.Fatalf("expected pid %d but received %d", pid, state.InitProcessPid) + } + if state.InitProcessStartTime != "010" { + t.Fatalf("expected process start time 010 but received %s", state.InitProcessStartTime) + } + paths := state.CgroupPaths + if paths == nil { + t.Fatal("cgroup paths should not be nil") + } + if memPath := paths["memory"]; memPath != expectedMemoryPath { + t.Fatalf("expected memory path %q but received %q", expectedMemoryPath, memPath) + } + for _, ns := range container.config.Namespaces { + path := state.NamespacePaths[string(ns.Type)] + if path == "" { + t.Fatalf("expected non nil namespace path for %s", ns.Type) + } + if ns.Type == configs.NEWNET { + if path != expectedNetworkPath { + t.Fatalf("expected path %q but received %q", expectedNetworkPath, path) + } + } else { + file := "" + switch ns.Type { + case configs.NEWNET: + file = "net" + case configs.NEWNS: + file = "mnt" + case configs.NEWPID: + file = "pid" + case configs.NEWIPC: + file = "ipc" + case configs.NEWUSER: + file = "user" + case configs.NEWUTS: + file = "uts" + } + expected := fmt.Sprintf("/proc/%d/ns/%s", pid, file) + if expected != path { + t.Fatalf("expected path %q but received %q", expected, path) + } + } + } +} diff --git a/linux_factory.go b/linux_factory.go index 16182927..66823953 100644 --- a/linux_factory.go +++ b/linux_factory.go @@ -18,8 +18,7 @@ import ( ) const ( - configFilename = "config.json" - stateFilename = "state.json" + stateFilename = "state.json" ) var ( @@ -65,23 +64,9 @@ func (l *linuxFactory) Create(id string, config *configs.Config) (Container, err } else if !os.IsNotExist(err) { return nil, newGenericError(err, SystemError) } - data, err := json.MarshalIndent(config, "", "\t") - if err != nil { - return nil, newGenericError(err, SystemError) - } if err := os.MkdirAll(containerRoot, 0700); err != nil { return nil, newGenericError(err, SystemError) } - f, err := os.Create(filepath.Join(containerRoot, configFilename)) - if err != nil { - os.RemoveAll(containerRoot) - return nil, newGenericError(err, SystemError) - } - defer f.Close() - if _, err := f.Write(data); err != nil { - os.RemoveAll(containerRoot) - return nil, newGenericError(err, SystemError) - } return &linuxContainer{ id: id, root: containerRoot, @@ -96,26 +81,20 @@ func (l *linuxFactory) Load(id string) (Container, error) { return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) } containerRoot := filepath.Join(l.root, id) - glog.Infof("loading container config from %s", containerRoot) - config, err := l.loadContainerConfig(containerRoot) - if err != nil { - return nil, err - } - glog.Infof("loading container state from %s", containerRoot) - state, err := l.loadContainerState(containerRoot) + state, err := l.loadState(containerRoot) if err != nil { return nil, err } r := &restoredProcess{ - processPid: state.InitPid, - processStartTime: state.InitStartTime, + processPid: state.InitProcessPid, + processStartTime: state.InitProcessStartTime, } - cgroupManager := cgroups.LoadCgroupManager(config.Cgroups, state.CgroupPaths) + cgroupManager := cgroups.LoadCgroupManager(state.Config.Cgroups, state.CgroupPaths) glog.Infof("using %s as cgroup manager", cgroupManager) return &linuxContainer{ initProcess: r, id: id, - config: config, + config: &state.Config, initArgs: l.initArgs, cgroupManager: cgroupManager, root: containerRoot, @@ -155,23 +134,7 @@ func (l *linuxFactory) StartInitialization(pipefd uintptr) (err error) { return i.Init() } -func (l *linuxFactory) loadContainerConfig(root string) (*configs.Config, error) { - f, err := os.Open(filepath.Join(root, configFilename)) - if err != nil { - if os.IsNotExist(err) { - return nil, newGenericError(err, ContainerNotExists) - } - return nil, newGenericError(err, SystemError) - } - defer f.Close() - var config *configs.Config - if err := json.NewDecoder(f).Decode(&config); err != nil { - return nil, newGenericError(err, ConfigInvalid) - } - return config, nil -} - -func (l *linuxFactory) loadContainerState(root string) (*configs.State, error) { +func (l *linuxFactory) loadState(root string) (*State, error) { f, err := os.Open(filepath.Join(root, stateFilename)) if err != nil { if os.IsNotExist(err) { @@ -180,7 +143,7 @@ func (l *linuxFactory) loadContainerState(root string) (*configs.State, error) { return nil, newGenericError(err, SystemError) } defer f.Close() - var state *configs.State + var state *State if err := json.NewDecoder(f).Decode(&state); err != nil { return nil, newGenericError(err, SystemError) } diff --git a/linux_factory_test.go b/linux_factory_test.go index 028c73e7..69001669 100644 --- a/linux_factory_test.go +++ b/linux_factory_test.go @@ -80,16 +80,14 @@ func TestFactoryLoadContainer(t *testing.T) { expectedConfig = &configs.Config{ Rootfs: "/mycontainer/root", } - expectedState = &configs.State{ - InitPid: 1024, + expectedState = &State{ + InitProcessPid: 1024, + Config: *expectedConfig, } ) if err := os.Mkdir(filepath.Join(root, id), 0700); err != nil { t.Fatal(err) } - if err := marshal(filepath.Join(root, id, configFilename), expectedConfig); err != nil { - t.Fatal(err) - } if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil { t.Fatal(err) } @@ -112,8 +110,8 @@ func TestFactoryLoadContainer(t *testing.T) { if !ok { t.Fatal("expected linux container on linux based systems") } - if lcontainer.initProcess.pid() != expectedState.InitPid { - t.Fatalf("expected init pid %d but received %d", expectedState.InitPid, lcontainer.initProcess.pid()) + if lcontainer.initProcess.pid() != expectedState.InitProcessPid { + t.Fatalf("expected init pid %d but received %d", expectedState.InitProcessPid, lcontainer.initProcess.pid()) } } diff --git a/linux_init.go b/linux_init.go index 7c56afe8..9ed27e89 100644 --- a/linux_init.go +++ b/linux_init.go @@ -9,11 +9,13 @@ import ( "strings" "syscall" + "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/netlink" "github.com/docker/libcontainer/system" "github.com/docker/libcontainer/user" "github.com/docker/libcontainer/utils" + "github.com/golang/glog" ) type initType string @@ -226,3 +228,35 @@ func setupRlimits(config *configs.Config) error { } return nil } + +// killCgroupProcesses freezes then itterates over all the processes inside the +// manager's cgroups sending a SIGKILL to each process then waiting for them to +// exit. +func killCgroupProcesses(m cgroups.Manager) error { + var procs []*os.Process + if err := m.Freeze(configs.Frozen); err != nil { + glog.Warning(err) + } + pids, err := m.GetPids() + if err != nil { + m.Freeze(configs.Thawed) + return err + } + for _, pid := range pids { + if p, err := os.FindProcess(pid); err == nil { + procs = append(procs, p) + if err := p.Kill(); err != nil { + glog.Warning(err) + } + } + } + if err := m.Freeze(configs.Thawed); err != nil { + glog.Warning(err) + } + for _, p := range procs { + if _, err := p.Wait(); err != nil { + glog.Warning(err) + } + } + return nil +} diff --git a/linux_network.go b/linux_network.go index 0b5d3394..e720dade 100644 --- a/linux_network.go +++ b/linux_network.go @@ -3,7 +3,6 @@ package libcontainer import ( - "errors" "fmt" "io/ioutil" "net" @@ -15,10 +14,6 @@ import ( "github.com/docker/libcontainer/utils" ) -var ( - ErrNotValidStrategyType = errors.New("not a valid network strategy type") -) - var strategies = map[string]networkStrategy{ "veth": &veth{}, "loopback": &loopback{}, @@ -32,19 +27,18 @@ type networkStrategy interface { } // getStrategy returns the specific network strategy for the -// provided type. If no strategy is registered for the type an -// ErrNotValidStrategyType is returned. +// provided type. func getStrategy(tpe string) (networkStrategy, error) { s, exists := strategies[tpe] if !exists { - return nil, ErrNotValidStrategyType + return nil, fmt.Errorf("unknown strategy type %q", tpe) } return s, nil } // Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. -func getNetworkInterfaceStats(interfaceName string) (*NetworkInterface, error) { - out := &NetworkInterface{Name: interfaceName} +func getNetworkInterfaceStats(interfaceName string) (*networkInterface, error) { + out := &networkInterface{Name: interfaceName} // This can happen if the network runtime information is missing - possible if the // container was created by an old version of libcontainer. if interfaceName == "" { diff --git a/notify_linux.go b/linux_notify.go similarity index 91% rename from notify_linux.go rename to linux_notify.go index 062fa11a..db51d57d 100644 --- a/notify_linux.go +++ b/linux_notify.go @@ -12,10 +12,10 @@ import ( const oomCgroupName = "memory" -// NotifyOnOOM returns channel on which you can expect event about OOM, +// notifyOnOOM returns channel on which you can expect event about OOM, // if process died without OOM this channel will be closed. // s is current *libcontainer.State for container. -func NotifyOnOOM(paths map[string]string) (<-chan struct{}, error) { +func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { dir := paths[oomCgroupName] if dir == "" { return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName) diff --git a/notify_linux_test.go b/linux_notify_test.go similarity index 98% rename from notify_linux_test.go rename to linux_notify_test.go index 65189d36..09bdf644 100644 --- a/notify_linux_test.go +++ b/linux_notify_test.go @@ -30,7 +30,7 @@ func TestNotifyOnOOM(t *testing.T) { paths := map[string]string{ "memory": memoryPath, } - ooms, err := NotifyOnOOM(paths) + ooms, err := notifyOnOOM(paths) if err != nil { t.Fatal("expected no error, got:", err) } diff --git a/linux_process.go b/linux_process.go index 2b434a51..ceef3c70 100644 --- a/linux_process.go +++ b/linux_process.go @@ -11,7 +11,6 @@ import ( "syscall" "github.com/docker/libcontainer/cgroups" - "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/system" "github.com/golang/glog" ) @@ -55,15 +54,15 @@ func (p *setnsProcess) signal(s os.Signal) error { func (p *setnsProcess) start() (err error) { defer p.parentPipe.Close() if p.forkedProcess, err = p.execSetns(); err != nil { - return err + return newSystemError(err) } if len(p.cgroupPaths) > 0 { if err := cgroups.EnterPid(p.cgroupPaths, p.forkedProcess.Pid); err != nil { - return err + return newSystemError(err) } } if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil { - return err + return newSystemError(err) } return nil } @@ -76,18 +75,18 @@ func (p *setnsProcess) execSetns() (*os.Process, error) { err := p.cmd.Start() p.childPipe.Close() if err != nil { - return nil, err + return nil, newSystemError(err) } status, err := p.cmd.Process.Wait() if err != nil { - return nil, err + return nil, newSystemError(err) } if !status.Success() { - return nil, &exec.ExitError{status} + return nil, newSystemError(&exec.ExitError{status}) } var pid *pid if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { - return nil, err + return nil, newSystemError(err) } return os.FindProcess(pid.Pid) } @@ -130,12 +129,12 @@ func (p *initProcess) start() error { err := p.cmd.Start() p.childPipe.Close() if err != nil { - return err + return newSystemError(err) } // Do this before syncing with child so that no children // can escape the cgroup if err := p.manager.Apply(p.pid()); err != nil { - return err + return newSystemError(err) } defer func() { if err != nil { @@ -144,13 +143,13 @@ func (p *initProcess) start() error { } }() if err := p.createNetworkInterfaces(); err != nil { - return err + return newSystemError(err) } // Start the setup process to setup the init process if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWUSER != 0 { parent, err := p.newUsernsSetupProcess() if err != nil { - return err + return newSystemError(err) } if err := parent.start(); err != nil { if err := parent.terminate(); err != nil { @@ -159,20 +158,20 @@ func (p *initProcess) start() error { return err } if _, err := parent.wait(); err != nil { - return err + return newSystemError(err) } } if err := p.sendConfig(); err != nil { - return err + return newSystemError(err) } // wait for the child process to fully complete and receive an error message // if one was encoutered var ierr *initError if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { - return err + return newSystemError(err) } if ierr != nil { - return ierr + return newSystemError(ierr) } return nil } @@ -184,27 +183,7 @@ func (p *initProcess) wait() (*os.ProcessState, error) { } // we should kill all processes in cgroup when init is died if we use host PID namespace if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWPID == 0 { - // TODO: this will not work for the success path because libcontainer - // does not wait on the process. This needs to be moved to destroy or add a Wait() - // method back onto the container. - var procs []*os.Process - p.manager.Freeze(configs.Frozen) - pids, err := p.manager.GetPids() - if err != nil { - return nil, err - } - for _, pid := range pids { - // TODO: log err without aborting if we are unable to find - // a single PID - if p, err := os.FindProcess(pid); err == nil { - procs = append(procs, p) - p.Kill() - } - } - p.manager.Freeze(configs.Thawed) - for _, p := range procs { - p.Wait() - } + killCgroupProcesses(p.manager) } return state, nil } @@ -253,7 +232,7 @@ func (p *initProcess) createNetworkInterfaces() error { func (p *initProcess) newUsernsSetupProcess() (parentProcess, error) { parentPipe, childPipe, err := newPipe() if err != nil { - return nil, err + return nil, newSystemError(err) } cmd := exec.Command(p.cmd.Args[0], p.cmd.Args[1:]...) cmd.ExtraFiles = []*os.File{childPipe} diff --git a/linux_userns_init.go b/linux_userns_init.go index a898f2d2..a7da9ce4 100644 --- a/linux_userns_init.go +++ b/linux_userns_init.go @@ -21,7 +21,8 @@ func (l *linuxUsernsInit) Init() error { } consolePath := l.config.Config.Console if consolePath != "" { - console := newConsoleFromPath(consolePath) + // TODO: why is this hard coded? + console := newConsoleFromPath("/dev/console") if err := console.dupStdio(); err != nil { return err } diff --git a/nsinit/config.go b/nsinit/config.go new file mode 100644 index 00000000..145fe59a --- /dev/null +++ b/nsinit/config.go @@ -0,0 +1,129 @@ +package main + +import ( + "bytes" + "encoding/json" + "io" + "os" + "path/filepath" + "syscall" + + "github.com/codegangsta/cli" + "github.com/docker/libcontainer/configs" +) + +var createFlags = []cli.Flag{ + cli.IntFlag{Name: "parent-death-signal", Usage: "set the signal that will be delivered to the process in case the parent dies"}, + cli.BoolFlag{Name: "read-only", Usage: "set the container's rootfs as read-only"}, + cli.StringSliceFlag{Name: "bind", Value: &cli.StringSlice{}, Usage: "add bind mounts to the container"}, + cli.StringSliceFlag{Name: "tmpfs", Value: &cli.StringSlice{}, Usage: "add tmpfs mounts to the container"}, + cli.IntFlag{Name: "cpushares", Usage: "set the cpushares for the container"}, + cli.IntFlag{Name: "memory-limit", Usage: "set the memory limit for the container"}, + cli.IntFlag{Name: "memory-swap", Usage: "set the memory swap limit for the container"}, + cli.StringFlag{Name: "cpuset-cpus", Usage: "set the cpuset cpus"}, + cli.StringFlag{Name: "cpuset-mems", Usage: "set the cpuset mems"}, + cli.StringFlag{Name: "apparmor-profile", Usage: "set the apparmor profile"}, + cli.StringFlag{Name: "process-label", Usage: "set the process label"}, + cli.StringFlag{Name: "mount-label", Usage: "set the mount label"}, +} + +var configCommand = cli.Command{ + Name: "config", + Usage: "generate a standard configuration file for a container", + Flags: append([]cli.Flag{ + cli.StringFlag{Name: "file,f", Value: "stdout", Usage: "write the configuration to the specified file"}, + }, createFlags...), + Action: func(context *cli.Context) { + template := getTemplate() + modify(template, context) + data, err := json.MarshalIndent(template, "", "\t") + if err != nil { + fatal(err) + } + var f *os.File + filePath := context.String("file") + switch filePath { + case "stdout", "": + f = os.Stdout + default: + if f, err = os.Create(filePath); err != nil { + fatal(err) + } + defer f.Close() + } + if _, err := io.Copy(f, bytes.NewBuffer(data)); err != nil { + fatal(err) + } + }, +} + +func modify(config *configs.Config, context *cli.Context) { + config.ParentDeathSignal = context.Int("parent-death-signal") + config.Readonlyfs = context.Bool("read-only") + config.Cgroups.CpusetCpus = context.String("cpuset-cpus") + config.Cgroups.CpusetMems = context.String("cpuset-mems") + config.Cgroups.CpuShares = int64(context.Int("cpushares")) + config.Cgroups.Memory = int64(context.Int("memory-limit")) + config.Cgroups.MemorySwap = int64(context.Int("memory-swap")) + config.AppArmorProfile = context.String("apparmor-profile") + config.ProcessLabel = context.String("process-label") + config.MountLabel = context.String("mount-label") +} + +func getTemplate() *configs.Config { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + return &configs.Config{ + Rootfs: cwd, + ParentDeathSignal: int(syscall.SIGKILL), + Capabilities: []string{ + "CHOWN", + "DAC_OVERRIDE", + "FSETID", + "FOWNER", + "MKNOD", + "NET_RAW", + "SETGID", + "SETUID", + "SETFCAP", + "SETPCAP", + "NET_BIND_SERVICE", + "SYS_CHROOT", + "KILL", + "AUDIT_WRITE", + }, + Namespaces: configs.Namespaces([]configs.Namespace{ + {Type: configs.NEWNS}, + {Type: configs.NEWUTS}, + {Type: configs.NEWIPC}, + {Type: configs.NEWPID}, + {Type: configs.NEWNET}, + }), + Cgroups: &configs.Cgroup{ + Name: filepath.Base(cwd), + Parent: "nsinit", + AllowAllDevices: false, + AllowedDevices: configs.DefaultAllowedDevices, + }, + + Devices: configs.DefaultAutoCreatedDevices, + Hostname: "nsinit", + Networks: []*configs.Network{ + { + Type: "loopback", + Address: "127.0.0.1/0", + Gateway: "localhost", + }, + }, + Rlimits: []configs.Rlimit{ + { + Type: syscall.RLIMIT_NOFILE, + Hard: 1024, + Soft: 1024, + }, + }, + } + +} diff --git a/nsinit/exec.go b/nsinit/exec.go index 6b90ce28..5c6b830f 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -20,13 +20,14 @@ var execCommand = cli.Command{ Name: "exec", Usage: "execute a new command inside a container", Action: execAction, - Flags: []cli.Flag{ + Flags: append([]cli.Flag{ cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"}, cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"}, + cli.BoolFlag{Name: "create", Usage: "create the container's configuration on the fly with arguments"}, cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"}, cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"}, - }, + }, createFlags...), } func execAction(context *cli.Context) { @@ -38,6 +39,7 @@ func execAction(context *cli.Context) { if err != nil { fatal(err) } + created := false container, err := factory.Load(context.String("id")) if err != nil { if lerr, ok := err.(libcontainer.Error); !ok || lerr.Code() != libcontainer.ContainerNotExists { @@ -45,10 +47,15 @@ func execAction(context *cli.Context) { } config, err := loadConfig(context) if err != nil { + tty.Close() fatal(err) } - config.Console = tty.console.Path() + if tty.console != nil { + config.Console = tty.console.Path() + } + created = true if container, err = factory.Create(context.String("id"), config); err != nil { + tty.Close() fatal(err) } } @@ -64,19 +71,26 @@ func execAction(context *cli.Context) { tty.attach(process) pid, err := container.Start(process) if err != nil { + tty.Close() fatal(err) } proc, err := os.FindProcess(pid) if err != nil { + tty.Close() fatal(err) } status, err := proc.Wait() if err != nil { + tty.Close() fatal(err) } - if err := container.Destroy(); err != nil { - fatal(err) + if created { + if err := container.Destroy(); err != nil { + tty.Close() + fatal(err) + } } + tty.Close() os.Exit(utils.ExitStatus(status.Sys().(syscall.WaitStatus))) } diff --git a/nsinit/init.go b/nsinit/init.go index 9848c42b..ea6295e5 100644 --- a/nsinit/init.go +++ b/nsinit/init.go @@ -12,9 +12,6 @@ import ( var initCommand = cli.Command{ Name: "init", Usage: "runs the init process inside the namespace", - Flags: []cli.Flag{ - cli.IntFlag{Name: "fd", Value: 0, Usage: "internal pipe fd"}, - }, Action: func(context *cli.Context) { runtime.GOMAXPROCS(1) runtime.LockOSThread() @@ -22,11 +19,7 @@ var initCommand = cli.Command{ if err != nil { log.Fatal(err) } - if context.Int("fd") == 0 { - log.Fatal("--fd must be specified for init process") - } - fd := uintptr(context.Int("fd")) - if err := factory.StartInitialization(fd); err != nil { + if err := factory.StartInitialization(3); err != nil { log.Fatal(err) } panic("This line should never been executed") diff --git a/nsinit/main.go b/nsinit/main.go index e0dcf460..a2afd00c 100644 --- a/nsinit/main.go +++ b/nsinit/main.go @@ -18,12 +18,14 @@ func main() { cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"}, } app.Commands = []cli.Command{ + configCommand, execCommand, initCommand, oomCommand, pauseCommand, statsCommand, unpauseCommand, + stateCommand, } if err := app.Run(os.Args); err != nil { log.Fatal(err) diff --git a/nsinit/oom.go b/nsinit/oom.go index eabe0b2b..c1b4c805 100644 --- a/nsinit/oom.go +++ b/nsinit/oom.go @@ -17,7 +17,7 @@ var oomCommand = cli.Command{ if err != nil { log.Fatal(err) } - n, err := container.OOM() + n, err := container.NotifyOOM() if err != nil { log.Fatal(err) } diff --git a/nsinit/state.go b/nsinit/state.go new file mode 100644 index 00000000..46981bb7 --- /dev/null +++ b/nsinit/state.go @@ -0,0 +1,31 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/codegangsta/cli" +) + +var stateCommand = cli.Command{ + Name: "state", + Usage: "get the container's current state", + Flags: []cli.Flag{ + cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, + }, + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + state, err := container.State() + if err != nil { + fatal(err) + } + data, err := json.MarshalIndent(state, "", "\t") + if err != nil { + fatal(err) + } + fmt.Printf("%s", data) + }, +} diff --git a/nsinit/stats.go b/nsinit/stats.go index 8320fed4..49087fa2 100644 --- a/nsinit/stats.go +++ b/nsinit/stats.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" "fmt" - "log" "github.com/codegangsta/cli" ) @@ -17,15 +16,15 @@ var statsCommand = cli.Command{ Action: func(context *cli.Context) { container, err := getContainer(context) if err != nil { - log.Fatal(err) + fatal(err) } stats, err := container.Stats() if err != nil { - log.Fatal(err) + fatal(err) } - data, jerr := json.MarshalIndent(stats, "", "\t") + data, err := json.MarshalIndent(stats, "", "\t") if err != nil { - log.Fatal(jerr) + fatal(err) } fmt.Printf("%s", data) }, diff --git a/nsinit/utils.go b/nsinit/utils.go index 901972e8..73c13b59 100644 --- a/nsinit/utils.go +++ b/nsinit/utils.go @@ -11,6 +11,11 @@ import ( ) func loadConfig(context *cli.Context) (*configs.Config, error) { + if context.Bool("create") { + config := getTemplate() + modify(config, context) + return config, nil + } f, err := os.Open(context.String("config")) if err != nil { return nil, err @@ -24,7 +29,7 @@ func loadConfig(context *cli.Context) (*configs.Config, error) { } func loadFactory(context *cli.Context) (libcontainer.Factory, error) { - return libcontainer.New(context.GlobalString("root"), []string{os.Args[0], "init", "--fd", "3", "--"}) + return libcontainer.New(context.GlobalString("root"), []string{os.Args[0], "init"}) } func getContainer(context *cli.Context) (libcontainer.Container, error) { diff --git a/stats.go b/stats.go index 198a8bf5..926d4d6b 100644 --- a/stats.go +++ b/stats.go @@ -2,7 +2,12 @@ package libcontainer import "github.com/docker/libcontainer/cgroups" -type NetworkInterface struct { +type Stats struct { + Interfaces []*networkInterface + CgroupStats *cgroups.Stats +} + +type networkInterface struct { // Name is the name of the network interface. Name string @@ -15,8 +20,3 @@ type NetworkInterface struct { TxErrors uint64 TxDropped uint64 } - -type Stats struct { - Interfaces []*NetworkInterface - CgroupStats *cgroups.Stats -}