Merge pull request #370 from crosbymichael/state

Ensure state is persisted
This commit is contained in:
Mrunal Patel 2015-02-12 11:19:58 -08:00
commit e2ed997ae5
32 changed files with 656 additions and 280 deletions

View File

@ -9,46 +9,46 @@ const (
) )
type Cgroup struct { type Cgroup struct {
Name string `json:"name,omitempty"` Name string `json:"name"`
// name of parent cgroup or slice // 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. // 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 limit (in bytes)
Memory int64 `json:"memory,omitempty"` Memory int64 `json:"memory"`
// Memory reservation or soft_limit (in bytes) // 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 // 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) // 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. // 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. // 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 // CPU to use
CpusetCpus string `json:"cpuset_cpus,omitempty"` CpusetCpus string `json:"cpuset_cpus"`
// MEM to use // MEM to use
CpusetMems string `json:"cpuset_mems,omitempty"` CpusetMems string `json:"cpuset_mems"`
// Specifies per cgroup weight, range is from 10 to 1000. // 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 // 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 // Parent slice to use for systemd TODO: remove in favor or parent
Slice string `json:"slice,omitempty"` Slice string `json:"slice"`
} }

View File

@ -3,98 +3,98 @@ package configs
import "fmt" import "fmt"
type Rlimit struct { type Rlimit struct {
Type int `json:"type,omitempty"` Type int `json:"type"`
Hard uint64 `json:"hard,omitempty"` Hard uint64 `json:"hard"`
Soft uint64 `json:"soft,omitempty"` Soft uint64 `json:"soft"`
} }
// IDMap represents UID/GID Mappings for User Namespaces. // IDMap represents UID/GID Mappings for User Namespaces.
type IDMap struct { type IDMap struct {
ContainerID int `json:"container_id,omitempty"` ContainerID int `json:"container_id"`
HostID int `json:"host_id,omitempty"` HostID int `json:"host_id"`
Size int `json:"size,omitempty"` Size int `json:"size"`
} }
// Config defines configuration options for executing a process inside a contained environment. // Config defines configuration options for executing a process inside a contained environment.
type Config struct { type Config struct {
// NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs // 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 // 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 // ParentDeathSignal specifies the signal that is sent to the container's process in the case
// that the parent process dies. // 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. // 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. // 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. // 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. // 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 // Readonlyfs will remount the container's rootfs as readonly where only externally mounted
// bind mounts are writtable. // 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 // Mounts specify additional source and destination paths that will be mounted inside the container's
// rootfs and mount namespace if specified // 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! // 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 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 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 // 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 // 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 // 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 // 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 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 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 // Cgroups specifies specific cgroup settings for the various subsystems that the container is
// placed into to limit the resources the container has available // 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 // AppArmorProfile specifies the profile to apply to the process running in the container and is
// change at the time the process is execed // 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 // ProcessLabel specifies the label to apply to the process running in the container. It is
// commonly used by selinux // 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 // RestrictSys will remount /proc/sys, /sys, and mask over sysrq-trigger as well as /proc/irq and
// /proc/bus // /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 // 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 // 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 // AdditionalGroups specifies the gids that should be added to supplementary groups
// in addition to those that the user belongs to. // 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 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 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 // Gets the root uid for the process on host which could be non-zero

View File

@ -11,28 +11,28 @@ const (
type Device struct { type Device struct {
// Device type, block, char, etc. // Device type, block, char, etc.
Type rune `json:"type,omitempty"` Type rune `json:"type"`
// Path to the device. // Path to the device.
Path string `json:"path,omitempty"` Path string `json:"path"`
// Major is the device's major number. // Major is the device's major number.
Major int64 `json:"major,omitempty"` Major int64 `json:"major"`
// Minor is the device's minor number. // Minor is the device's minor number.
Minor int64 `json:"minor,omitempty"` Minor int64 `json:"minor"`
// Cgroup permissions format, rwm. // Cgroup permissions format, rwm.
Permissions string `json:"permissions,omitempty"` Permissions string `json:"permissions"`
// FileMode permission bits for the device. // FileMode permission bits for the device.
FileMode os.FileMode `json:"file_mode,omitempty"` FileMode os.FileMode `json:"file_mode"`
// Uid of the device. // Uid of the device.
Uid uint32 `json:"uid,omitempty"` Uid uint32 `json:"uid"`
// Gid of the device. // Gid of the device.
Gid uint32 `json:"gid,omitempty"` Gid uint32 `json:"gid"`
} }
func (d *Device) CgroupString() string { func (d *Device) CgroupString() string {

View File

@ -1,11 +1,11 @@
package configs package configs
type Mount struct { type Mount struct {
Type string `json:"type,omitempty"` Type string `json:"type"`
Source string `json:"source,omitempty"` // Source path, in the host namespace Source string `json:"source"` // Source path, in the host namespace
Destination string `json:"destination,omitempty"` // Destination path, in the container Destination string `json:"destination"` // Destination path, in the container
Writable bool `json:"writable,omitempty"` Writable bool `json:"writable"`
Relabel string `json:"relabel,omitempty"` // Relabel source if set, "z" indicates shared, "Z" indicates unshared Relabel string `json:"relabel"` // Relabel source if set, "z" indicates shared, "Z" indicates unshared
Private bool `json:"private,omitempty"` Private bool `json:"private"`
Slave bool `json:"slave,omitempty"` Slave bool `json:"slave"`
} }

View File

@ -1,6 +1,7 @@
package configs package configs
import ( import (
"fmt"
"syscall" "syscall"
) )
@ -19,13 +20,39 @@ const (
// alternate path that is able to be joined via setns. // alternate path that is able to be joined via setns.
type Namespace struct { type Namespace struct {
Type NamespaceType `json:"type"` Type NamespaceType `json:"type"`
Path string `json:"path,omitempty"` Path string `json:"path"`
} }
func (n *Namespace) Syscall() int { func (n *Namespace) Syscall() int {
return namespaceInfo[n.Type] 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 type Namespaces []Namespace
func (n *Namespaces) Remove(t NamespaceType) bool { func (n *Namespaces) Remove(t NamespaceType) bool {

View File

@ -6,42 +6,42 @@ package configs
// container to be setup with the host's networking stack // container to be setup with the host's networking stack
type Network struct { type Network struct {
// Type sets the networks type, commonly veth and loopback // Type sets the networks type, commonly veth and loopback
Type string `json:"type,omitempty"` Type string `json:"type"`
// Name of the network interface // Name of the network interface
Name string `json:"name,omitempty"` Name string `json:"name"`
// The bridge to use. // 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 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 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 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 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 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 // 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 // container's interfaces if a pair is created, specifically in the case of type veth
// Note: This does not apply to loopback interfaces. // 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 // 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 // container's interfaces if a pair is created, specifically in the case of type veth
// Note: This does not apply to loopback interfaces. // 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 // HostInterfaceName is a unique name of a veth pair that resides on in the host interface of the
// container. // 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 // 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. // destination of 0.0.0.0(or *) when viewed in the route table.
type Route struct { type Route struct {
// Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6 // 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 // 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 // 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 // The device to set this route up for, for example: eth0
InterfaceName string `json:"interface_name,omitempty"` InterfaceName string `json:"interface_name"`
} }

View File

@ -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"`
}

View File

@ -2,7 +2,7 @@ package libcontainer
import "io" import "io"
// Console is a psuedo TTY. // Console represents a pseudo TTY.
type Console interface { type Console interface {
io.ReadWriter io.ReadWriter
io.Closer io.Closer

View File

@ -1,6 +1,7 @@
/* // Libcontainer provides a native Go implementation for creating containers
NOTE: The API is in flux and mainly not implemented. Proceed with caution until further notice. // 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 package libcontainer
import ( import (
@ -9,6 +10,46 @@ import (
"github.com/docker/libcontainer/configs" "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. // A libcontainer container object.
// //
// Each container is thread-safe within the same process. Since a container can // 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. // Returns the current status of the container.
// //
// errors: // errors:
// ContainerDestroyed - Container no longer exists,
// Systemerror - System error. // 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. // Returns the current config of the container.
Config() configs.Config Config() configs.Config
@ -89,9 +137,9 @@ type Container interface {
// Systemerror - System error. // Systemerror - System error.
Signal(signal os.Signal) 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: // errors:
// Systemerror - System error. // Systemerror - System error.
OOM() (<-chan struct{}, error) NotifyOOM() (<-chan struct{}, error)
} }

View File

@ -15,6 +15,7 @@ const (
ContainerNotExists ContainerNotExists
ContainerPaused ContainerPaused
ContainerNotStopped ContainerNotStopped
ContainerNotRunning
// Common errors // Common errors
ConfigInvalid ConfigInvalid
@ -36,7 +37,9 @@ func (c ErrorCode) String() string {
case ContainerNotExists: case ContainerNotExists:
return "Container does not exist" return "Container does not exist"
case ContainerNotStopped: case ContainerNotStopped:
return "Container isn't stopped" return "Container is not stopped"
case ContainerNotRunning:
return "Container is not running"
default: default:
return "Unknown error" return "Unknown error"
} }

View File

@ -11,38 +11,55 @@ import (
var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}}
Code: {{.ECode}} Code: {{.ECode}}
{{if .Err }}
Message: {{.Err.Error}} Message: {{.Err.Error}}
{{end}}
Frames:{{range $i, $frame := .Stack.Frames}} Frames:{{range $i, $frame := .Stack.Frames}}
--- ---
{{$i}}: {{$frame.Function}} {{$i}}: {{$frame.Function}}
Package: {{$frame.Package}} Package: {{$frame.Package}}
File: {{$frame.File}}{{end}} File: {{$frame.File}}@{{$frame.Line}}{{end}}
`)) `))
func newGenericError(err error, c ErrorCode) Error { func newGenericError(err error, c ErrorCode) Error {
return &GenericError{ if le, ok := err.(Error); ok {
return le
}
return &genericError{
Timestamp: time.Now(), Timestamp: time.Now(),
Err: err, Err: err,
ECode: c, 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 Timestamp time.Time
ECode ErrorCode ECode ErrorCode
Err error Err error
Stack stacktrace.Stacktrace 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) 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 return e.ECode
} }
func (e *GenericError) Detail(w io.Writer) error { func (e *genericError) Detail(w io.Writer) error {
return errorTemplate.Execute(w, e) return errorTemplate.Execute(w, e)
} }

View File

@ -383,7 +383,7 @@ func TestFreeze(t *testing.T) {
if err := container.Resume(); err != nil { if err := container.Resume(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if state != configs.Paused { if state != libcontainer.Paused {
t.Fatal("Unexpected state: ", state) t.Fatal("Unexpected state: ", state)
} }

View File

@ -12,6 +12,8 @@ import (
"github.com/docker/libcontainer/label" "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) { func NewConsole() (Console, error) {
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
if err != nil { if err != nil {

View File

@ -3,9 +3,11 @@
package libcontainer package libcontainer
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"syscall" "syscall"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
@ -32,29 +34,54 @@ func (c *linuxContainer) Config() configs.Config {
return *c.config return *c.config
} }
func (c *linuxContainer) Status() (configs.Status, error) { func (c *linuxContainer) Status() (Status, error) {
if c.initProcess == nil { if c.initProcess == nil {
return configs.Destroyed, nil return Destroyed, nil
} }
// return Running if the init process is alive // return Running if the init process is alive
if err := syscall.Kill(c.initProcess.pid(), 0); err != nil { if err := syscall.Kill(c.initProcess.pid(), 0); err != nil {
if err == syscall.ESRCH { if err == syscall.ESRCH {
return configs.Destroyed, nil return Destroyed, nil
} }
return 0, err return 0, newSystemError(err)
} }
if c.config.Cgroups != nil && if c.config.Cgroups != nil && c.config.Cgroups.Freezer == configs.Frozen {
c.config.Cgroups.Freezer == configs.Frozen { return Paused, nil
return configs.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) { func (c *linuxContainer) Processes() ([]int, error) {
glog.Info("fetch container processes") glog.Info("fetch container processes")
pids, err := c.cgroupManager.GetPids() pids, err := c.cgroupManager.GetPids()
if err != nil { if err != nil {
return nil, newGenericError(err, SystemError) return nil, newSystemError(err)
} }
return pids, nil return pids, nil
} }
@ -66,14 +93,14 @@ func (c *linuxContainer) Stats() (*Stats, error) {
stats = &Stats{} stats = &Stats{}
) )
if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil { if stats.CgroupStats, err = c.cgroupManager.GetStats(); err != nil {
return stats, newGenericError(err, SystemError) return stats, newSystemError(err)
} }
for _, iface := range c.config.Networks { for _, iface := range c.config.Networks {
switch iface.Type { switch iface.Type {
case "veth": case "veth":
istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) istats, err := getNetworkInterfaceStats(iface.HostInterfaceName)
if err != nil { if err != nil {
return stats, newGenericError(err, SystemError) return stats, newSystemError(err)
} }
stats.Interfaces = append(stats.Interfaces, istats) stats.Interfaces = append(stats.Interfaces, istats)
} }
@ -86,20 +113,20 @@ func (c *linuxContainer) Start(process *Process) (int, error) {
if err != nil { if err != nil {
return -1, err return -1, err
} }
doInit := status == configs.Destroyed doInit := status == Destroyed
parent, err := c.newParentProcess(process, doInit) parent, err := c.newParentProcess(process, doInit)
if err != nil { if err != nil {
return -1, err return -1, newSystemError(err)
} }
if err := parent.start(); err != nil { if err := parent.start(); err != nil {
// terminate the process to ensure that it properly is reaped. // terminate the process to ensure that it properly is reaped.
if err := parent.terminate(); err != nil { if err := parent.terminate(); err != nil {
glog.Warning(err) glog.Warning(err)
} }
return -1, err return -1, newSystemError(err)
} }
if doInit { if doInit {
c.initProcess = parent c.updateState(parent)
} }
return parent.pid(), nil 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) { func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
parentPipe, childPipe, err := newPipe() parentPipe, childPipe, err := newPipe()
if err != nil { if err != nil {
return nil, err return nil, newSystemError(err)
} }
cmd, err := c.commandTemplate(p, childPipe) cmd, err := c.commandTemplate(p, childPipe)
if err != nil { if err != nil {
return nil, err return nil, newSystemError(err)
} }
if !doInit { if !doInit {
return c.newSetnsProcess(p, cmd, parentPipe, childPipe), nil 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.SysProcAttr = &syscall.SysProcAttr{}
} }
cmd.ExtraFiles = []*os.File{childPipe} 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 return cmd, nil
} }
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) *initProcess { func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) *initProcess {
t := "_LIBCONTAINER_INITTYPE=standard" t := "_LIBCONTAINER_INITTYPE=standard"
cloneFlags := c.config.Namespaces.CloneFlags() cloneFlags := c.config.Namespaces.CloneFlags()
if cloneFlags&syscall.CLONE_NEWUSER != 0 { if cloneFlags&syscall.CLONE_NEWUSER != 0 {
c.addUidGidMappings(cmd.SysProcAttr) c.addUidGidMappings(cmd.SysProcAttr)
@ -176,6 +205,8 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
Config: c.config, Config: c.config,
Args: process.Args, Args: process.Args,
Env: process.Env, Env: process.Env,
User: process.User,
Cwd: process.Cwd,
} }
} }
@ -212,11 +243,20 @@ func (c *linuxContainer) Destroy() error {
if err != nil { if err != nil {
return err return err
} }
if status != configs.Destroyed { if status != Destroyed {
return newGenericError(nil, ContainerNotStopped) return newGenericError(nil, ContainerNotStopped)
} }
// TODO: remove cgroups if !c.config.Namespaces.Contains(configs.NEWPID) {
return os.RemoveAll(c.root) 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 { func (c *linuxContainer) Pause() error {
@ -228,11 +268,26 @@ func (c *linuxContainer) Resume() error {
} }
func (c *linuxContainer) Signal(signal os.Signal) 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) return c.initProcess.signal(signal)
} }
// TODO: rename to be more descriptive func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
func (c *linuxContainer) OOM() (<-chan struct{}, error) { return notifyOnOOM(c.cgroupManager.GetPaths())
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)
} }

View File

@ -3,6 +3,8 @@
package libcontainer package libcontainer
import ( import (
"fmt"
"os"
"testing" "testing"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
@ -12,6 +14,7 @@ import (
type mockCgroupManager struct { type mockCgroupManager struct {
pids []int pids []int
stats *cgroups.Stats stats *cgroups.Stats
paths map[string]string
} }
func (m *mockCgroupManager) GetPids() ([]int, error) { func (m *mockCgroupManager) GetPids() ([]int, error) {
@ -31,25 +34,52 @@ func (m *mockCgroupManager) Destroy() error {
} }
func (m *mockCgroupManager) GetPaths() map[string]string { func (m *mockCgroupManager) GetPaths() map[string]string {
return nil return m.paths
} }
func (m *mockCgroupManager) Freeze(state configs.FreezerState) error { func (m *mockCgroupManager) Freeze(state configs.FreezerState) error {
return nil 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) { func TestGetContainerPids(t *testing.T) {
container := &linuxContainer{ container := &linuxContainer{
id: "myid", id: "myid",
config: &configs.Config{}, config: &configs.Config{},
cgroupManager: &mockCgroupManager{pids: []int{1, 2, 3}}, cgroupManager: &mockCgroupManager{pids: []int{1, 2, 3}},
} }
pids, err := container.Processes() pids, err := container.Processes()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for i, expected := range []int{1, 2, 3} { for i, expected := range []int{1, 2, 3} {
if pids[i] != expected { if pids[i] != expected {
t.Fatalf("expected pid %d but received %d", expected, pids[i]) t.Fatalf("expected pid %d but received %d", expected, pids[i])
@ -70,7 +100,6 @@ func TestGetContainerStats(t *testing.T) {
}, },
}, },
} }
stats, err := container.Stats() stats, err := container.Stats()
if err != nil { if err != nil {
t.Fatal(err) 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) 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)
}
}
}
}

View File

@ -18,8 +18,7 @@ import (
) )
const ( const (
configFilename = "config.json" stateFilename = "state.json"
stateFilename = "state.json"
) )
var ( var (
@ -65,23 +64,9 @@ func (l *linuxFactory) Create(id string, config *configs.Config) (Container, err
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
return nil, newGenericError(err, SystemError) 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 { if err := os.MkdirAll(containerRoot, 0700); err != nil {
return nil, newGenericError(err, SystemError) 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{ return &linuxContainer{
id: id, id: id,
root: containerRoot, root: containerRoot,
@ -96,26 +81,20 @@ func (l *linuxFactory) Load(id string) (Container, error) {
return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid)
} }
containerRoot := filepath.Join(l.root, id) containerRoot := filepath.Join(l.root, id)
glog.Infof("loading container config from %s", containerRoot) state, err := l.loadState(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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
r := &restoredProcess{ r := &restoredProcess{
processPid: state.InitPid, processPid: state.InitProcessPid,
processStartTime: state.InitStartTime, 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) glog.Infof("using %s as cgroup manager", cgroupManager)
return &linuxContainer{ return &linuxContainer{
initProcess: r, initProcess: r,
id: id, id: id,
config: config, config: &state.Config,
initArgs: l.initArgs, initArgs: l.initArgs,
cgroupManager: cgroupManager, cgroupManager: cgroupManager,
root: containerRoot, root: containerRoot,
@ -155,23 +134,7 @@ func (l *linuxFactory) StartInitialization(pipefd uintptr) (err error) {
return i.Init() return i.Init()
} }
func (l *linuxFactory) loadContainerConfig(root string) (*configs.Config, error) { func (l *linuxFactory) loadState(root string) (*State, 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) {
f, err := os.Open(filepath.Join(root, stateFilename)) f, err := os.Open(filepath.Join(root, stateFilename))
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -180,7 +143,7 @@ func (l *linuxFactory) loadContainerState(root string) (*configs.State, error) {
return nil, newGenericError(err, SystemError) return nil, newGenericError(err, SystemError)
} }
defer f.Close() defer f.Close()
var state *configs.State var state *State
if err := json.NewDecoder(f).Decode(&state); err != nil { if err := json.NewDecoder(f).Decode(&state); err != nil {
return nil, newGenericError(err, SystemError) return nil, newGenericError(err, SystemError)
} }

View File

@ -80,16 +80,14 @@ func TestFactoryLoadContainer(t *testing.T) {
expectedConfig = &configs.Config{ expectedConfig = &configs.Config{
Rootfs: "/mycontainer/root", Rootfs: "/mycontainer/root",
} }
expectedState = &configs.State{ expectedState = &State{
InitPid: 1024, InitProcessPid: 1024,
Config: *expectedConfig,
} }
) )
if err := os.Mkdir(filepath.Join(root, id), 0700); err != nil { if err := os.Mkdir(filepath.Join(root, id), 0700); err != nil {
t.Fatal(err) 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 { if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -112,8 +110,8 @@ func TestFactoryLoadContainer(t *testing.T) {
if !ok { if !ok {
t.Fatal("expected linux container on linux based systems") t.Fatal("expected linux container on linux based systems")
} }
if lcontainer.initProcess.pid() != expectedState.InitPid { if lcontainer.initProcess.pid() != expectedState.InitProcessPid {
t.Fatalf("expected init pid %d but received %d", expectedState.InitPid, lcontainer.initProcess.pid()) t.Fatalf("expected init pid %d but received %d", expectedState.InitProcessPid, lcontainer.initProcess.pid())
} }
} }

View File

@ -9,11 +9,13 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/netlink" "github.com/docker/libcontainer/netlink"
"github.com/docker/libcontainer/system" "github.com/docker/libcontainer/system"
"github.com/docker/libcontainer/user" "github.com/docker/libcontainer/user"
"github.com/docker/libcontainer/utils" "github.com/docker/libcontainer/utils"
"github.com/golang/glog"
) )
type initType string type initType string
@ -226,3 +228,35 @@ func setupRlimits(config *configs.Config) error {
} }
return nil 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
}

View File

@ -3,7 +3,6 @@
package libcontainer package libcontainer
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@ -15,10 +14,6 @@ import (
"github.com/docker/libcontainer/utils" "github.com/docker/libcontainer/utils"
) )
var (
ErrNotValidStrategyType = errors.New("not a valid network strategy type")
)
var strategies = map[string]networkStrategy{ var strategies = map[string]networkStrategy{
"veth": &veth{}, "veth": &veth{},
"loopback": &loopback{}, "loopback": &loopback{},
@ -32,19 +27,18 @@ type networkStrategy interface {
} }
// getStrategy returns the specific network strategy for the // getStrategy returns the specific network strategy for the
// provided type. If no strategy is registered for the type an // provided type.
// ErrNotValidStrategyType is returned.
func getStrategy(tpe string) (networkStrategy, error) { func getStrategy(tpe string) (networkStrategy, error) {
s, exists := strategies[tpe] s, exists := strategies[tpe]
if !exists { if !exists {
return nil, ErrNotValidStrategyType return nil, fmt.Errorf("unknown strategy type %q", tpe)
} }
return s, nil return s, nil
} }
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. // Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
func getNetworkInterfaceStats(interfaceName string) (*NetworkInterface, error) { func getNetworkInterfaceStats(interfaceName string) (*networkInterface, error) {
out := &NetworkInterface{Name: interfaceName} out := &networkInterface{Name: interfaceName}
// This can happen if the network runtime information is missing - possible if the // This can happen if the network runtime information is missing - possible if the
// container was created by an old version of libcontainer. // container was created by an old version of libcontainer.
if interfaceName == "" { if interfaceName == "" {

View File

@ -12,10 +12,10 @@ import (
const oomCgroupName = "memory" 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. // if process died without OOM this channel will be closed.
// s is current *libcontainer.State for container. // 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] dir := paths[oomCgroupName]
if dir == "" { if dir == "" {
return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName) return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName)

View File

@ -30,7 +30,7 @@ func TestNotifyOnOOM(t *testing.T) {
paths := map[string]string{ paths := map[string]string{
"memory": memoryPath, "memory": memoryPath,
} }
ooms, err := NotifyOnOOM(paths) ooms, err := notifyOnOOM(paths)
if err != nil { if err != nil {
t.Fatal("expected no error, got:", err) t.Fatal("expected no error, got:", err)
} }

View File

@ -11,7 +11,6 @@ import (
"syscall" "syscall"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/system" "github.com/docker/libcontainer/system"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -55,15 +54,15 @@ func (p *setnsProcess) signal(s os.Signal) error {
func (p *setnsProcess) start() (err error) { func (p *setnsProcess) start() (err error) {
defer p.parentPipe.Close() defer p.parentPipe.Close()
if p.forkedProcess, err = p.execSetns(); err != nil { if p.forkedProcess, err = p.execSetns(); err != nil {
return err return newSystemError(err)
} }
if len(p.cgroupPaths) > 0 { if len(p.cgroupPaths) > 0 {
if err := cgroups.EnterPid(p.cgroupPaths, p.forkedProcess.Pid); err != nil { 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 { if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil {
return err return newSystemError(err)
} }
return nil return nil
} }
@ -76,18 +75,18 @@ func (p *setnsProcess) execSetns() (*os.Process, error) {
err := p.cmd.Start() err := p.cmd.Start()
p.childPipe.Close() p.childPipe.Close()
if err != nil { if err != nil {
return nil, err return nil, newSystemError(err)
} }
status, err := p.cmd.Process.Wait() status, err := p.cmd.Process.Wait()
if err != nil { if err != nil {
return nil, err return nil, newSystemError(err)
} }
if !status.Success() { if !status.Success() {
return nil, &exec.ExitError{status} return nil, newSystemError(&exec.ExitError{status})
} }
var pid *pid var pid *pid
if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil { if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil {
return nil, err return nil, newSystemError(err)
} }
return os.FindProcess(pid.Pid) return os.FindProcess(pid.Pid)
} }
@ -130,12 +129,12 @@ func (p *initProcess) start() error {
err := p.cmd.Start() err := p.cmd.Start()
p.childPipe.Close() p.childPipe.Close()
if err != nil { if err != nil {
return err return newSystemError(err)
} }
// Do this before syncing with child so that no children // Do this before syncing with child so that no children
// can escape the cgroup // can escape the cgroup
if err := p.manager.Apply(p.pid()); err != nil { if err := p.manager.Apply(p.pid()); err != nil {
return err return newSystemError(err)
} }
defer func() { defer func() {
if err != nil { if err != nil {
@ -144,13 +143,13 @@ func (p *initProcess) start() error {
} }
}() }()
if err := p.createNetworkInterfaces(); err != nil { if err := p.createNetworkInterfaces(); err != nil {
return err return newSystemError(err)
} }
// Start the setup process to setup the init process // Start the setup process to setup the init process
if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWUSER != 0 { if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWUSER != 0 {
parent, err := p.newUsernsSetupProcess() parent, err := p.newUsernsSetupProcess()
if err != nil { if err != nil {
return err return newSystemError(err)
} }
if err := parent.start(); err != nil { if err := parent.start(); err != nil {
if err := parent.terminate(); err != nil { if err := parent.terminate(); err != nil {
@ -159,20 +158,20 @@ func (p *initProcess) start() error {
return err return err
} }
if _, err := parent.wait(); err != nil { if _, err := parent.wait(); err != nil {
return err return newSystemError(err)
} }
} }
if err := p.sendConfig(); err != nil { if err := p.sendConfig(); err != nil {
return err return newSystemError(err)
} }
// wait for the child process to fully complete and receive an error message // wait for the child process to fully complete and receive an error message
// if one was encoutered // if one was encoutered
var ierr *initError var ierr *initError
if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF {
return err return newSystemError(err)
} }
if ierr != nil { if ierr != nil {
return ierr return newSystemError(ierr)
} }
return nil 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 // 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 { if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWPID == 0 {
// TODO: this will not work for the success path because libcontainer killCgroupProcesses(p.manager)
// 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()
}
} }
return state, nil return state, nil
} }
@ -253,7 +232,7 @@ func (p *initProcess) createNetworkInterfaces() error {
func (p *initProcess) newUsernsSetupProcess() (parentProcess, error) { func (p *initProcess) newUsernsSetupProcess() (parentProcess, error) {
parentPipe, childPipe, err := newPipe() parentPipe, childPipe, err := newPipe()
if err != nil { if err != nil {
return nil, err return nil, newSystemError(err)
} }
cmd := exec.Command(p.cmd.Args[0], p.cmd.Args[1:]...) cmd := exec.Command(p.cmd.Args[0], p.cmd.Args[1:]...)
cmd.ExtraFiles = []*os.File{childPipe} cmd.ExtraFiles = []*os.File{childPipe}

View File

@ -21,7 +21,8 @@ func (l *linuxUsernsInit) Init() error {
} }
consolePath := l.config.Config.Console consolePath := l.config.Config.Console
if consolePath != "" { if consolePath != "" {
console := newConsoleFromPath(consolePath) // TODO: why is this hard coded?
console := newConsoleFromPath("/dev/console")
if err := console.dupStdio(); err != nil { if err := console.dupStdio(); err != nil {
return err return err
} }

129
nsinit/config.go Normal file
View File

@ -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,
},
},
}
}

View File

@ -20,13 +20,14 @@ var execCommand = cli.Command{
Name: "exec", Name: "exec",
Usage: "execute a new command inside a container", Usage: "execute a new command inside a container",
Action: execAction, Action: execAction,
Flags: []cli.Flag{ Flags: append([]cli.Flag{
cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"}, 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: "id", Value: "nsinit", Usage: "specify the ID for a container"},
cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"}, 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.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"}, cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"},
}, }, createFlags...),
} }
func execAction(context *cli.Context) { func execAction(context *cli.Context) {
@ -38,6 +39,7 @@ func execAction(context *cli.Context) {
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
created := false
container, err := factory.Load(context.String("id")) container, err := factory.Load(context.String("id"))
if err != nil { if err != nil {
if lerr, ok := err.(libcontainer.Error); !ok || lerr.Code() != libcontainer.ContainerNotExists { if lerr, ok := err.(libcontainer.Error); !ok || lerr.Code() != libcontainer.ContainerNotExists {
@ -45,10 +47,15 @@ func execAction(context *cli.Context) {
} }
config, err := loadConfig(context) config, err := loadConfig(context)
if err != nil { if err != nil {
tty.Close()
fatal(err) 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 { if container, err = factory.Create(context.String("id"), config); err != nil {
tty.Close()
fatal(err) fatal(err)
} }
} }
@ -64,19 +71,26 @@ func execAction(context *cli.Context) {
tty.attach(process) tty.attach(process)
pid, err := container.Start(process) pid, err := container.Start(process)
if err != nil { if err != nil {
tty.Close()
fatal(err) fatal(err)
} }
proc, err := os.FindProcess(pid) proc, err := os.FindProcess(pid)
if err != nil { if err != nil {
tty.Close()
fatal(err) fatal(err)
} }
status, err := proc.Wait() status, err := proc.Wait()
if err != nil { if err != nil {
tty.Close()
fatal(err) fatal(err)
} }
if err := container.Destroy(); err != nil { if created {
fatal(err) if err := container.Destroy(); err != nil {
tty.Close()
fatal(err)
}
} }
tty.Close()
os.Exit(utils.ExitStatus(status.Sys().(syscall.WaitStatus))) os.Exit(utils.ExitStatus(status.Sys().(syscall.WaitStatus)))
} }

View File

@ -12,9 +12,6 @@ import (
var initCommand = cli.Command{ var initCommand = cli.Command{
Name: "init", Name: "init",
Usage: "runs the init process inside the namespace", 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) { Action: func(context *cli.Context) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
runtime.LockOSThread() runtime.LockOSThread()
@ -22,11 +19,7 @@ var initCommand = cli.Command{
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if context.Int("fd") == 0 { if err := factory.StartInitialization(3); err != nil {
log.Fatal("--fd must be specified for init process")
}
fd := uintptr(context.Int("fd"))
if err := factory.StartInitialization(fd); err != nil {
log.Fatal(err) log.Fatal(err)
} }
panic("This line should never been executed") panic("This line should never been executed")

View File

@ -18,12 +18,14 @@ func main() {
cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"}, cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"},
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
configCommand,
execCommand, execCommand,
initCommand, initCommand,
oomCommand, oomCommand,
pauseCommand, pauseCommand,
statsCommand, statsCommand,
unpauseCommand, unpauseCommand,
stateCommand,
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -17,7 +17,7 @@ var oomCommand = cli.Command{
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
n, err := container.OOM() n, err := container.NotifyOOM()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

31
nsinit/state.go Normal file
View File

@ -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)
},
}

View File

@ -3,7 +3,6 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
) )
@ -17,15 +16,15 @@ var statsCommand = cli.Command{
Action: func(context *cli.Context) { Action: func(context *cli.Context) {
container, err := getContainer(context) container, err := getContainer(context)
if err != nil { if err != nil {
log.Fatal(err) fatal(err)
} }
stats, err := container.Stats() stats, err := container.Stats()
if err != nil { if err != nil {
log.Fatal(err) fatal(err)
} }
data, jerr := json.MarshalIndent(stats, "", "\t") data, err := json.MarshalIndent(stats, "", "\t")
if err != nil { if err != nil {
log.Fatal(jerr) fatal(err)
} }
fmt.Printf("%s", data) fmt.Printf("%s", data)
}, },

View File

@ -11,6 +11,11 @@ import (
) )
func loadConfig(context *cli.Context) (*configs.Config, error) { 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")) f, err := os.Open(context.String("config"))
if err != nil { if err != nil {
return nil, err return nil, err
@ -24,7 +29,7 @@ func loadConfig(context *cli.Context) (*configs.Config, error) {
} }
func loadFactory(context *cli.Context) (libcontainer.Factory, 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) { func getContainer(context *cli.Context) (libcontainer.Container, error) {

View File

@ -2,7 +2,12 @@ package libcontainer
import "github.com/docker/libcontainer/cgroups" 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 is the name of the network interface.
Name string Name string
@ -15,8 +20,3 @@ type NetworkInterface struct {
TxErrors uint64 TxErrors uint64
TxDropped uint64 TxDropped uint64
} }
type Stats struct {
Interfaces []*NetworkInterface
CgroupStats *cgroups.Stats
}