Persist container state to disk
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
31327166e5
commit
5c246d038f
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ 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 {
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -1,18 +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
|
||||
)
|
33
container.go
33
container.go
|
@ -9,21 +9,44 @@ 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
|
||||
InitProcessPid int `json:"init_process_pid"`
|
||||
|
||||
// InitProcessStartTime is the init process start time.
|
||||
InitProcessStartTime string
|
||||
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
|
||||
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
|
||||
NamespacePaths map[string]string `json:"namespace_paths"`
|
||||
|
||||
// Config is the container's configuration.
|
||||
Config configs.Config `json:"config"`
|
||||
}
|
||||
|
||||
// A libcontainer container object.
|
||||
|
@ -40,7 +63,7 @@ type Container interface {
|
|||
// errors:
|
||||
// ContainerDestroyed - Container no longer exists,
|
||||
// Systemerror - System error.
|
||||
Status() (configs.Status, error)
|
||||
Status() (Status, error)
|
||||
|
||||
// State returns the current container's state information.
|
||||
//
|
||||
|
|
|
@ -20,6 +20,9 @@ File: {{$frame.File}}{{end}}
|
|||
`))
|
||||
|
||||
func newGenericError(err error, c ErrorCode) Error {
|
||||
if le, ok := err.(Error); ok {
|
||||
return le
|
||||
}
|
||||
return &GenericError{
|
||||
Timestamp: time.Now(),
|
||||
Err: err,
|
||||
|
@ -28,6 +31,18 @@ func newGenericError(err error, c ErrorCode) Error {
|
|||
}
|
||||
}
|
||||
|
||||
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(2),
|
||||
}
|
||||
}
|
||||
|
||||
type GenericError struct {
|
||||
Timestamp time.Time
|
||||
ECode ErrorCode
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
package libcontainer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
|
@ -32,21 +34,21 @@ 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
|
||||
return Paused, nil
|
||||
}
|
||||
return configs.Running, nil
|
||||
return Running, nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) State() (*State, error) {
|
||||
|
@ -54,14 +56,16 @@ func (c *linuxContainer) State() (*State, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status == configs.Destroyed {
|
||||
if status == Destroyed {
|
||||
return nil, newGenericError(fmt.Errorf("container destroyed"), ContainerNotExists)
|
||||
}
|
||||
startTime, err := c.initProcess.startTime()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, newSystemError(err)
|
||||
}
|
||||
state := &State{
|
||||
ID: c.ID(),
|
||||
Config: *c.config,
|
||||
InitProcessPid: c.initProcess.pid(),
|
||||
InitProcessStartTime: startTime,
|
||||
CgroupPaths: c.cgroupManager.GetPaths(),
|
||||
|
@ -96,7 +100,7 @@ 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
|
||||
}
|
||||
|
@ -108,14 +112,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)
|
||||
}
|
||||
|
@ -128,20 +132,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
|
||||
}
|
||||
|
@ -149,11 +153,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
|
||||
|
@ -171,7 +175,10 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
|||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.ExtraFiles = []*os.File{childPipe}
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
||||
if c.config.ParentDeathSignal > 0 {
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal)
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
|
@ -254,11 +261,19 @@ 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
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *linuxContainer) Pause() error {
|
||||
|
@ -270,11 +285,23 @@ 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())
|
||||
return c.initProcess.signal(signal)
|
||||
}
|
||||
|
||||
// TODO: rename to be more descriptive
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
configFilename = "config.json"
|
||||
stateFilename = "state.json"
|
||||
)
|
||||
|
||||
|
@ -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,13 +81,7 @@ 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
|
||||
}
|
||||
|
@ -110,12 +89,12 @@ func (l *linuxFactory) Load(id string) (Container, error) {
|
|||
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) (*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) {
|
||||
|
|
|
@ -82,14 +82,12 @@ func TestFactoryLoadContainer(t *testing.T) {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -24,7 +24,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) {
|
||||
|
|
Loading…
Reference in New Issue