Merge pull request #370 from crosbymichael/state
Ensure state is persisted
This commit is contained in:
commit
e2ed997ae5
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
60
container.go
60
container.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
5
error.go
5
error.go
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 == "" {
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
},
|
||||||
|
}
|
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
12
stats.go
12
stats.go
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue