2014-01-08 21:47:57 -05:00
package execdriver
import (
2015-03-05 12:55:14 -05:00
"encoding/json"
2014-01-13 19:18:46 -05:00
"errors"
2014-02-21 16:27:15 -05:00
"io"
2015-03-05 12:55:14 -05:00
"io/ioutil"
2014-02-21 16:27:15 -05:00
"os"
2014-01-09 18:04:45 -05:00
"os/exec"
2015-03-05 12:55:14 -05:00
"path/filepath"
"strconv"
2015-01-30 12:29:46 -05:00
"strings"
2015-01-07 17:43:04 -05:00
"time"
2015-02-11 14:21:38 -05:00
"github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/libcontainer"
2015-03-05 12:55:14 -05:00
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/configs"
2014-01-08 21:47:57 -05:00
)
2014-03-18 16:49:16 -04:00
// Context is a generic key value pair that allows
// arbatrary data to be sent
type Context map [ string ] string
2014-01-13 19:18:46 -05:00
var (
2015-01-07 21:02:08 -05:00
ErrNotRunning = errors . New ( "Container is not running" )
2014-01-13 21:36:59 -05:00
ErrWaitTimeoutReached = errors . New ( "Wait timeout reached" )
ErrDriverAlreadyRegistered = errors . New ( "A driver already registered this docker init function" )
ErrDriverNotFound = errors . New ( "The requested docker init has not been found" )
2014-01-13 19:18:46 -05:00
)
2014-08-26 18:44:00 -04:00
type StartCallback func ( * ProcessConfig , int )
2014-01-13 18:02:12 -05:00
2014-01-15 16:57:07 -05:00
// Driver specific information based on
// processes registered with the driver
2014-01-15 14:46:25 -05:00
type Info interface {
IsRunning ( ) bool
}
2014-02-21 16:27:15 -05:00
// Terminal in an interface for drivers to implement
// if they want to support Close and Resize calls from
// the core
type Terminal interface {
io . Closer
Resize ( height , width int ) error
}
type TtyTerminal interface {
2015-03-05 12:55:14 -05:00
Master ( ) libcontainer . Console
2014-02-21 16:27:15 -05:00
}
2014-10-08 13:03:57 -04:00
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
// Whether the container encountered an OOM.
OOMKilled bool
}
2014-01-09 18:04:45 -05:00
type Driver interface {
2014-10-30 19:06:54 -04:00
Run ( c * Command , pipes * Pipes , startCallback StartCallback ) ( ExitStatus , error ) // Run executes the process and blocks until the process exits and returns the exit code
2014-10-08 13:03:57 -04:00
// Exec executes the process in an existing container, blocks until the process exits and returns the exit code
2014-09-04 01:29:19 -04:00
Exec ( c * Command , processConfig * ProcessConfig , pipes * Pipes , startCallback StartCallback ) ( int , error )
2014-01-20 19:05:07 -05:00
Kill ( c * Command , sig int ) error
2014-05-21 17:06:18 -04:00
Pause ( c * Command ) error
Unpause ( c * Command ) error
2014-01-28 10:17:51 -05:00
Name ( ) string // Driver name
Info ( id string ) Info // "temporary" hack (until we move state from core to plugins)
GetPidsForContainer ( id string ) ( [ ] int , error ) // Returns a list of pids for the given container.
2014-03-26 02:48:16 -04:00
Terminate ( c * Command ) error // kill it with fire
2014-09-10 03:34:38 -04:00
Clean ( id string ) error // clean all traces of container exec
2015-01-07 17:43:04 -05:00
Stats ( id string ) ( * ResourceStats , error ) // Get resource stats for a running container
2014-01-09 18:04:45 -05:00
}
2014-01-08 21:47:57 -05:00
// Network settings of the container
type Network struct {
2014-05-02 17:17:31 -04:00
Interface * NetworkInterface ` json:"interface" ` // if interface is nil then networking is disabled
Mtu int ` json:"mtu" `
ContainerID string ` json:"container_id" ` // id of the container to join network.
HostNetworking bool ` json:"host_networking" `
2014-03-16 15:52:27 -04:00
}
2014-11-10 16:14:17 -05:00
// IPC settings of the container
type Ipc struct {
ContainerID string ` json:"container_id" ` // id of the container to join ipc.
HostIpc bool ` json:"host_ipc" `
}
2014-11-25 15:10:53 -05:00
// PID settings of the container
type Pid struct {
HostPid bool ` json:"host_pid" `
}
2014-03-16 15:52:27 -04:00
type NetworkInterface struct {
2015-01-08 18:03:19 -05:00
Gateway string ` json:"gateway" `
IPAddress string ` json:"ip" `
IPPrefixLen int ` json:"ip_prefix_len" `
MacAddress string ` json:"mac" `
Bridge string ` json:"bridge" `
GlobalIPv6Address string ` json:"global_ipv6" `
LinkLocalIPv6Address string ` json:"link_local_ipv6" `
GlobalIPv6PrefixLen int ` json:"global_ipv6_prefix_len" `
IPv6Gateway string ` json:"ipv6_gateway" `
2014-01-08 21:47:57 -05:00
}
2014-01-20 16:23:02 -05:00
type Resources struct {
2015-02-11 14:21:38 -05:00
Memory int64 ` json:"memory" `
MemorySwap int64 ` json:"memory_swap" `
CpuShares int64 ` json:"cpu_shares" `
move resources from Config to HostConfig
Cgroup resources are host dependent, they should be in hostConfig.
For backward compatibility, we just copy it to hostConfig, and leave it in
Config for now, so there is no regressions, but the right way to use this
throught json is to put it in HostConfig, like:
{
"Hostname": "",
...
"HostConfig": {
"CpuShares": 512,
"Memory": 314572800,
...
}
}
As we will add CpusetMems, CpusetCpus is definitely a better name, but some
users are already using Cpuset in their http APIs, we also make it compatible.
The main idea is keep using Cpuset in Config Struct, and make it has the same
value as CpusetCpus, but not always, some scenarios:
- Users use --cpuset in docker command, it can setup cpuset.cpus and can
get Cpuset field from docker inspect or other http API which will get
config info.
- Users use --cpuset-cpus in docker command, ditto.
- Users use Cpuset field in their http APIs, ditto.
- Users use CpusetCpus field in their http APIs, they won't get Cpuset field
in Config info, because by then, they should already know what happens
to Cpuset.
Signed-off-by: Qiang Huang <h.huangqiang@huawei.com>
2015-03-10 21:31:18 -04:00
CpusetCpus string ` json:"cpuset_cpus" `
2015-04-14 21:33:46 -04:00
CpusetMems string ` json:"cpuset_mems" `
2015-02-11 14:21:38 -05:00
Rlimits [ ] * ulimit . Rlimit ` json:"rlimits" `
2014-01-20 16:23:02 -05:00
}
2015-01-07 17:43:04 -05:00
type ResourceStats struct {
2015-03-05 12:55:14 -05:00
* libcontainer . Stats
2015-01-07 19:22:42 -05:00
Read time . Time ` json:"read" `
MemoryLimit int64 ` json:"memory_limit" `
SystemUsage uint64 ` json:"system_usage" `
2015-01-07 17:43:04 -05:00
}
2014-03-03 10:15:29 -05:00
type Mount struct {
Source string ` json:"source" `
Destination string ` json:"destination" `
Writable bool ` json:"writable" `
Private bool ` json:"private" `
2014-09-13 12:42:10 -04:00
Slave bool ` json:"slave" `
2014-03-03 10:15:29 -05:00
}
2014-08-26 18:05:37 -04:00
// Describes a process that will be run inside a container.
type ProcessConfig struct {
2014-01-20 19:05:07 -05:00
exec . Cmd ` json:"-" `
2014-01-10 14:44:35 -05:00
2014-08-26 18:44:00 -04:00
Privileged bool ` json:"privileged" `
User string ` json:"user" `
Tty bool ` json:"tty" `
Entrypoint string ` json:"entrypoint" `
Arguments [ ] string ` json:"arguments" `
Terminal Terminal ` json:"-" ` // standard or tty terminal
Console string ` json:"-" ` // dev/console path
2014-08-26 18:05:37 -04:00
}
// Process wrapps an os/exec.Cmd to add more metadata
type Command struct {
2014-09-29 18:40:26 -04:00
ID string ` json:"id" `
2015-01-13 16:52:51 -05:00
Rootfs string ` json:"rootfs" ` // root fs of the container
ReadonlyRootfs bool ` json:"readonly_rootfs" `
2014-09-29 18:40:26 -04:00
InitPath string ` json:"initpath" ` // dockerinit
WorkingDir string ` json:"working_dir" `
ConfigPath string ` json:"config_path" ` // this should be able to be removed when the lxc template is moved into the driver
Network * Network ` json:"network" `
2014-11-10 16:14:17 -05:00
Ipc * Ipc ` json:"ipc" `
2014-11-25 15:10:53 -05:00
Pid * Pid ` json:"pid" `
2014-09-29 18:40:26 -04:00
Resources * Resources ` json:"resources" `
Mounts [ ] Mount ` json:"mounts" `
2015-03-05 12:55:14 -05:00
AllowedDevices [ ] * configs . Device ` json:"allowed_devices" `
AutoCreatedDevices [ ] * configs . Device ` json:"autocreated_devices" `
2014-09-29 18:40:26 -04:00
CapAdd [ ] string ` json:"cap_add" `
CapDrop [ ] string ` json:"cap_drop" `
ContainerPid int ` json:"container_pid" ` // the pid for the process inside a container
ProcessConfig ProcessConfig ` json:"process_config" ` // Describes the init process of the container.
ProcessLabel string ` json:"process_label" `
MountLabel string ` json:"mount_label" `
LxcConfig [ ] string ` json:"lxc_config" `
2014-09-29 19:34:45 -04:00
AppArmorProfile string ` json:"apparmor_profile" `
2015-03-16 18:42:15 -04:00
CgroupParent string ` json:"cgroup_parent" ` // The parent cgroup for this command.
2014-01-09 18:04:45 -05:00
}
2015-01-30 12:29:46 -05:00
2015-03-05 12:55:14 -05:00
func InitContainer ( c * Command ) * configs . Config {
2015-01-30 12:29:46 -05:00
container := template . New ( )
container . Hostname = getEnv ( "HOSTNAME" , c . ProcessConfig . Env )
container . Cgroups . Name = c . ID
container . Cgroups . AllowedDevices = c . AllowedDevices
2015-03-05 12:55:14 -05:00
container . Devices = c . AutoCreatedDevices
container . Rootfs = c . Rootfs
container . Readonlyfs = c . ReadonlyRootfs
2015-01-30 12:29:46 -05:00
// check to see if we are running in ramdisk to disable pivot root
2015-03-05 12:55:14 -05:00
container . NoPivotRoot = os . Getenv ( "DOCKER_RAMDISK" ) != ""
2015-03-16 18:42:15 -04:00
// Default parent cgroup is "docker". Override if required.
if c . CgroupParent != "" {
container . Cgroups . Parent = c . CgroupParent
}
2015-01-30 12:29:46 -05:00
return container
}
func getEnv ( key string , env [ ] string ) string {
for _ , pair := range env {
parts := strings . Split ( pair , "=" )
if parts [ 0 ] == key {
return parts [ 1 ]
}
}
return ""
}
2015-03-05 12:55:14 -05:00
func SetupCgroups ( container * configs . Config , c * Command ) error {
2015-01-30 12:29:46 -05:00
if c . Resources != nil {
container . Cgroups . CpuShares = c . Resources . CpuShares
container . Cgroups . Memory = c . Resources . Memory
container . Cgroups . MemoryReservation = c . Resources . Memory
container . Cgroups . MemorySwap = c . Resources . MemorySwap
move resources from Config to HostConfig
Cgroup resources are host dependent, they should be in hostConfig.
For backward compatibility, we just copy it to hostConfig, and leave it in
Config for now, so there is no regressions, but the right way to use this
throught json is to put it in HostConfig, like:
{
"Hostname": "",
...
"HostConfig": {
"CpuShares": 512,
"Memory": 314572800,
...
}
}
As we will add CpusetMems, CpusetCpus is definitely a better name, but some
users are already using Cpuset in their http APIs, we also make it compatible.
The main idea is keep using Cpuset in Config Struct, and make it has the same
value as CpusetCpus, but not always, some scenarios:
- Users use --cpuset in docker command, it can setup cpuset.cpus and can
get Cpuset field from docker inspect or other http API which will get
config info.
- Users use --cpuset-cpus in docker command, ditto.
- Users use Cpuset field in their http APIs, ditto.
- Users use CpusetCpus field in their http APIs, they won't get Cpuset field
in Config info, because by then, they should already know what happens
to Cpuset.
Signed-off-by: Qiang Huang <h.huangqiang@huawei.com>
2015-03-10 21:31:18 -04:00
container . Cgroups . CpusetCpus = c . Resources . CpusetCpus
2015-04-14 21:33:46 -04:00
container . Cgroups . CpusetMems = c . Resources . CpusetMems
2015-01-30 12:29:46 -05:00
}
return nil
}
2015-03-05 12:55:14 -05:00
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
func getNetworkInterfaceStats ( interfaceName string ) ( * libcontainer . NetworkInterface , error ) {
out := & libcontainer . NetworkInterface { Name : interfaceName }
// This can happen if the network runtime information is missing - possible if the
// container was created by an old version of libcontainer.
if interfaceName == "" {
return out , nil
}
type netStatsPair struct {
// Where to write the output.
Out * uint64
// The network stats file to read.
File string
}
// Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
netStats := [ ] netStatsPair {
{ Out : & out . RxBytes , File : "tx_bytes" } ,
{ Out : & out . RxPackets , File : "tx_packets" } ,
{ Out : & out . RxErrors , File : "tx_errors" } ,
{ Out : & out . RxDropped , File : "tx_dropped" } ,
{ Out : & out . TxBytes , File : "rx_bytes" } ,
{ Out : & out . TxPackets , File : "rx_packets" } ,
{ Out : & out . TxErrors , File : "rx_errors" } ,
{ Out : & out . TxDropped , File : "rx_dropped" } ,
}
for _ , netStat := range netStats {
data , err := readSysfsNetworkStats ( interfaceName , netStat . File )
if err != nil {
return nil , err
2015-01-30 12:29:46 -05:00
}
2015-03-05 12:55:14 -05:00
* ( netStat . Out ) = data
}
return out , nil
}
// Reads the specified statistics available under /sys/class/net/<EthInterface>/statistics
func readSysfsNetworkStats ( ethInterface , statsFile string ) ( uint64 , error ) {
data , err := ioutil . ReadFile ( filepath . Join ( "/sys/class/net" , ethInterface , "statistics" , statsFile ) )
if err != nil {
return 0 , err
}
return strconv . ParseUint ( strings . TrimSpace ( string ( data ) ) , 10 , 64 )
}
func Stats ( containerDir string , containerMemoryLimit int64 , machineMemory int64 ) ( * ResourceStats , error ) {
f , err := os . Open ( filepath . Join ( containerDir , "state.json" ) )
if err != nil {
return nil , err
}
defer f . Close ( )
type network struct {
Type string
HostInterfaceName string
}
state := struct {
CgroupPaths map [ string ] string ` json:"cgroup_paths" `
Networks [ ] network
} { }
if err := json . NewDecoder ( f ) . Decode ( & state ) ; err != nil {
2015-01-30 12:29:46 -05:00
return nil , err
}
now := time . Now ( )
2015-03-05 12:55:14 -05:00
mgr := fs . Manager { Paths : state . CgroupPaths }
cstats , err := mgr . GetStats ( )
2015-01-30 12:29:46 -05:00
if err != nil {
return nil , err
}
2015-03-05 12:55:14 -05:00
stats := & libcontainer . Stats { CgroupStats : cstats }
2015-01-30 12:29:46 -05:00
// if the container does not have any memory limit specified set the
// limit to the machines memory
memoryLimit := containerMemoryLimit
if memoryLimit == 0 {
memoryLimit = machineMemory
}
2015-03-05 12:55:14 -05:00
for _ , iface := range state . Networks {
switch iface . Type {
case "veth" :
istats , err := getNetworkInterfaceStats ( iface . HostInterfaceName )
if err != nil {
return nil , err
}
stats . Interfaces = append ( stats . Interfaces , istats )
}
}
2015-01-30 12:29:46 -05:00
return & ResourceStats {
2015-03-05 12:55:14 -05:00
Stats : stats ,
Read : now ,
MemoryLimit : memoryLimit ,
2015-01-30 12:29:46 -05:00
} , nil
}