Remove containerd files
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
992fdbfd76
commit
e115b52ce2
74 changed files with 0 additions and 9757 deletions
|
@ -1,136 +0,0 @@
|
|||
// Code generated by protoc-gen-gogo.
|
||||
// source: container.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package container is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
container.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Container
|
||||
ContainerSpec
|
||||
*/
|
||||
package container
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
import docker_containerkit_types "github.com/docker/containerkit/api/types/mount"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
const _ = proto.GoGoProtoPackageIsVersion1
|
||||
|
||||
type Container struct {
|
||||
Container *ContainerSpec `protobuf:"bytes,1,opt,name=container" json:"container,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Container) Reset() { *m = Container{} }
|
||||
func (m *Container) String() string { return proto.CompactTextString(m) }
|
||||
func (*Container) ProtoMessage() {}
|
||||
func (*Container) Descriptor() ([]byte, []int) { return fileDescriptorContainer, []int{0} }
|
||||
|
||||
func (m *Container) GetContainer() *ContainerSpec {
|
||||
if m != nil {
|
||||
return m.Container
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Container specifies runtime parameters for a container.
|
||||
type ContainerSpec struct {
|
||||
// name must be a unique name to identify the container.
|
||||
//
|
||||
// This can be used as a system identifier external to ContainerKit services.
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
// Labels defines labels to be added to the container at creation time. If
|
||||
// collisions with system labels occur, these labels will be overridden.
|
||||
//
|
||||
// This field *must* remain compatible with the Labels field of
|
||||
// Annotations.
|
||||
Labels map[string]string `protobuf:"bytes,2,rep,name=labels" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
Mounts []docker_containerkit_types.Mount `protobuf:"bytes,3,rep,name=mounts" json:"mounts"`
|
||||
// Command to run the the container. The first element is a path to the
|
||||
// executable and the following elements are treated as arguments.
|
||||
//
|
||||
// If command is empty, execution will fall back to the image's entrypoint.
|
||||
//
|
||||
// Command should only be used when overriding entrypoint.
|
||||
Command []string `protobuf:"bytes,4,rep,name=command" json:"command,omitempty"`
|
||||
// Args specifies arguments provided to the image's entrypoint.
|
||||
//
|
||||
// If Command and Args are provided, Args will be appended to Command.
|
||||
Args []string `protobuf:"bytes,5,rep,name=args" json:"args,omitempty"`
|
||||
// Env specifies the environment variables for the container in NAME=VALUE
|
||||
// format. These must be compliant with [IEEE Std
|
||||
// 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html).
|
||||
Env []string `protobuf:"bytes,6,rep,name=env" json:"env,omitempty"`
|
||||
// Dir defines the working directory to set for the container process.
|
||||
Dir string `protobuf:"bytes,7,opt,name=dir,proto3" json:"dir,omitempty"`
|
||||
// User specifies the user that should be employed to run the container.
|
||||
//
|
||||
// Note that the primary group may be specified by appending the group name
|
||||
// or id to the user name, separated by a `:`. This syntax is
|
||||
// `<user>:<group>`.
|
||||
User string `protobuf:"bytes,8,opt,name=user,proto3" json:"user,omitempty"`
|
||||
// Groups specifies supplementary groups available to the user.
|
||||
Groups []string `protobuf:"bytes,9,rep,name=groups" json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ContainerSpec) Reset() { *m = ContainerSpec{} }
|
||||
func (m *ContainerSpec) String() string { return proto.CompactTextString(m) }
|
||||
func (*ContainerSpec) ProtoMessage() {}
|
||||
func (*ContainerSpec) Descriptor() ([]byte, []int) { return fileDescriptorContainer, []int{1} }
|
||||
|
||||
func (m *ContainerSpec) GetLabels() map[string]string {
|
||||
if m != nil {
|
||||
return m.Labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ContainerSpec) GetMounts() []docker_containerkit_types.Mount {
|
||||
if m != nil {
|
||||
return m.Mounts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Container)(nil), "docker.containerkit.v1.Container")
|
||||
proto.RegisterType((*ContainerSpec)(nil), "docker.containerkit.v1.ContainerSpec")
|
||||
}
|
||||
|
||||
var fileDescriptorContainer = []byte{
|
||||
// 344 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x91, 0xc1, 0x4a, 0xf3, 0x40,
|
||||
0x14, 0x85, 0xff, 0x26, 0x6d, 0xfa, 0xe7, 0x16, 0x51, 0x06, 0x29, 0x43, 0x37, 0x86, 0x82, 0xd0,
|
||||
0x8d, 0x13, 0x5a, 0x37, 0xda, 0x85, 0x0b, 0x8b, 0x0b, 0x41, 0x41, 0xe2, 0x13, 0x4c, 0xd2, 0x31,
|
||||
0x86, 0x36, 0x33, 0x61, 0x32, 0x29, 0xf4, 0x0d, 0x7d, 0x0a, 0xb7, 0xbe, 0x86, 0xcc, 0x9d, 0xb4,
|
||||
0x56, 0xe8, 0xc2, 0x4d, 0x38, 0xe7, 0xe4, 0x9e, 0x2f, 0x73, 0x27, 0x70, 0x9a, 0x29, 0x69, 0x78,
|
||||
0x21, 0x85, 0x66, 0x95, 0x56, 0x46, 0x91, 0xe1, 0x52, 0x65, 0x2b, 0xa1, 0xd9, 0x3e, 0x5f, 0x15,
|
||||
0x86, 0x6d, 0xa6, 0xa3, 0xab, 0xbc, 0x30, 0xef, 0x4d, 0xca, 0x32, 0x55, 0xc6, 0xb9, 0xca, 0x55,
|
||||
0x8c, 0xe3, 0x69, 0xf3, 0x86, 0x0e, 0x0d, 0x2a, 0x87, 0x19, 0xcd, 0x0f, 0xc6, 0x1d, 0x31, 0x3e,
|
||||
0x24, 0xc6, 0xbc, 0x2a, 0x62, 0xb3, 0xad, 0x44, 0x1d, 0x97, 0xaa, 0x91, 0xc6, 0x3d, 0x5d, 0x77,
|
||||
0xfc, 0x02, 0xe1, 0x62, 0x37, 0x4b, 0x16, 0x10, 0xee, 0x8b, 0xb4, 0x13, 0x75, 0x26, 0x83, 0xd9,
|
||||
0x25, 0x3b, 0x7e, 0x46, 0xb6, 0x6f, 0xbd, 0x56, 0x22, 0x4b, 0x7e, 0x7a, 0xe3, 0x2f, 0x0f, 0x4e,
|
||||
0x7e, 0xbd, 0x24, 0x04, 0xba, 0x92, 0x97, 0x02, 0x89, 0x61, 0x82, 0x9a, 0x3c, 0x42, 0xb0, 0xe6,
|
||||
0xa9, 0x58, 0xd7, 0xd4, 0x8b, 0xfc, 0xc9, 0x60, 0x36, 0xfd, 0xd3, 0x77, 0xd8, 0x13, 0x76, 0x1e,
|
||||
0xa4, 0xd1, 0xdb, 0xa4, 0x05, 0x90, 0x3b, 0x08, 0x70, 0xa3, 0x9a, 0xfa, 0x88, 0x8a, 0x8e, 0xa2,
|
||||
0xf0, 0x02, 0xd8, 0xb3, 0x1d, 0xbc, 0xef, 0x7e, 0x7c, 0x5e, 0xfc, 0x4b, 0xda, 0x16, 0xa1, 0xd0,
|
||||
0xcf, 0x54, 0x59, 0x72, 0xb9, 0xa4, 0xdd, 0xc8, 0x9f, 0x84, 0xc9, 0xce, 0xda, 0x83, 0x73, 0x9d,
|
||||
0xd7, 0xb4, 0x87, 0x31, 0x6a, 0x72, 0x06, 0xbe, 0x90, 0x1b, 0x1a, 0x60, 0x64, 0xa5, 0x4d, 0x96,
|
||||
0x85, 0xa6, 0x7d, 0xdc, 0xce, 0x4a, 0xdb, 0x6b, 0x6a, 0xa1, 0xe9, 0x7f, 0xb7, 0xb0, 0xd5, 0x64,
|
||||
0x08, 0x41, 0xae, 0x55, 0x53, 0xd5, 0x34, 0xc4, 0x6a, 0xeb, 0x46, 0xb7, 0x30, 0x38, 0x58, 0xca,
|
||||
0xc2, 0x56, 0x62, 0xdb, 0x5e, 0x95, 0x95, 0xe4, 0x1c, 0x7a, 0x1b, 0xbe, 0x6e, 0x04, 0xf5, 0x30,
|
||||
0x73, 0x66, 0xee, 0xdd, 0x74, 0xd2, 0x00, 0x7f, 0xe1, 0xf5, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0x4a, 0x36, 0x9c, 0x33, 0x58, 0x02, 0x00, 0x00,
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package docker.containerkit.v1;
|
||||
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
import "github.com/docker/containerkit/api/types/mount/mount.proto";
|
||||
|
||||
service Containers {
|
||||
rpc Create(CreateRequest) returns (CreateResponse);
|
||||
rpc Start(StartRequest) returns (StartResponse);
|
||||
rpc Stop(StopRequest) returns (StopResponse);
|
||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
||||
rpc List(ListRequest) returns (ListResponse);
|
||||
rpc State(StateRequest) returns (StateResponse);
|
||||
rpc Exec(ExecRequest) returns (ExecResponse);
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse);
|
||||
}
|
||||
|
||||
message Container {
|
||||
ContainerSpec container = 1;
|
||||
// Runtime properties go here.
|
||||
}
|
||||
|
||||
// Container specifies runtime parameters for a container.
|
||||
message ContainerSpec {
|
||||
// name must be a unique name to identify the container.
|
||||
//
|
||||
// This can be used as a system identifier external to ContainerKit services.
|
||||
string name = 1;
|
||||
|
||||
// Labels defines labels to be added to the container at creation time. If
|
||||
// collisions with system labels occur, these labels will be overridden.
|
||||
//
|
||||
// This field *must* remain compatible with the Labels field of
|
||||
// Annotations.
|
||||
map<string, string> labels = 2;
|
||||
|
||||
repeated types.Mount mounts = 3 [(gogoproto.nullable) = false];
|
||||
|
||||
// Command to run the the container. The first element is a path to the
|
||||
// executable and the following elements are treated as arguments.
|
||||
//
|
||||
// If command is empty, execution will fall back to the image's entrypoint.
|
||||
//
|
||||
// Command should only be used when overriding entrypoint.
|
||||
repeated string command = 4;
|
||||
|
||||
// Args specifies arguments provided to the image's entrypoint.
|
||||
//
|
||||
// If Command and Args are provided, Args will be appended to Command.
|
||||
repeated string args = 5;
|
||||
|
||||
// Env specifies the environment variables for the container in NAME=VALUE
|
||||
// format. These must be compliant with [IEEE Std
|
||||
// 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html).
|
||||
repeated string env = 6;
|
||||
|
||||
// Dir defines the working directory to set for the container process.
|
||||
string dir = 7;
|
||||
|
||||
// User specifies the user that should be employed to run the container.
|
||||
//
|
||||
// Note that the primary group may be specified by appending the group name
|
||||
// or id to the user name, separated by a `:`. This syntax is
|
||||
// `<user>:<group>`.
|
||||
string user = 8;
|
||||
|
||||
// Groups specifies supplementary groups available to the user.
|
||||
repeated string groups = 9;
|
||||
}
|
||||
|
||||
message Rlimit {
|
||||
string type = 1;
|
||||
uint64 soft = 2;
|
||||
uint64 hard = 3;
|
||||
}
|
||||
|
||||
message User {
|
||||
uint32 uid = 1;
|
||||
uint32 gid = 2;
|
||||
repeated uint32 additionalGids = 3;
|
||||
}
|
||||
|
||||
message CreateRequest {
|
||||
string id = 1;
|
||||
string image = 2;
|
||||
repeated string args = 3;
|
||||
repeated string env = 4;
|
||||
}
|
||||
|
||||
message CreateResponse {
|
||||
Container container = 1;
|
||||
}
|
||||
|
||||
message StartRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message StartResponse {
|
||||
|
||||
}
|
||||
|
||||
message StopRequest {
|
||||
string id = 1;
|
||||
uint32 signal = 2;
|
||||
uint32 timeout = 3;
|
||||
}
|
||||
|
||||
message StopResponse {
|
||||
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message DeleteResponse {
|
||||
|
||||
}
|
||||
|
||||
message ListRequest {
|
||||
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Container containers = 1;
|
||||
}
|
||||
|
||||
message StateRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message StateResponse {
|
||||
Container container = 1;
|
||||
}
|
||||
|
||||
message ExecRequest {
|
||||
string id = 1;
|
||||
bool terminal = 2;
|
||||
User user = 3;
|
||||
repeated string args = 4;
|
||||
repeated string env = 5;
|
||||
string cwd = 6;
|
||||
string pid = 7;
|
||||
repeated string capabilities = 8;
|
||||
string apparmorProfile = 9;
|
||||
string selinuxLabel = 10;
|
||||
bool noNewPrivileges = 11;
|
||||
repeated Rlimit rlimits = 12;
|
||||
}
|
||||
|
||||
message ExecResponse {
|
||||
|
||||
}
|
||||
|
||||
message UpdateRequest {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message UpdateResponse {
|
||||
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package container
|
||||
|
||||
//go:generate protoc -I .:../../..:$GOPATH/src --gogo_out=plugins=grpc,import_path=github.com/docker/containerkit/api/container:. container.proto
|
|
@ -1,437 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/containerd/osutils"
|
||||
"github.com/docker/containerd/runtime"
|
||||
"github.com/docker/containerd/supervisor"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type apiServer struct {
|
||||
sv *supervisor.Supervisor
|
||||
}
|
||||
|
||||
// NewServer returns grpc server instance
|
||||
func NewServer(sv *supervisor.Supervisor) types.APIServer {
|
||||
return &apiServer{
|
||||
sv: sv,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiServer) GetServerVersion(ctx context.Context, c *types.GetServerVersionRequest) (*types.GetServerVersionResponse, error) {
|
||||
return &types.GetServerVersionResponse{
|
||||
Major: containerd.VersionMajor,
|
||||
Minor: containerd.VersionMinor,
|
||||
Patch: containerd.VersionPatch,
|
||||
Revision: containerd.GitCommit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContainerRequest) (*types.CreateContainerResponse, error) {
|
||||
if c.BundlePath == "" {
|
||||
return nil, errors.New("empty bundle path")
|
||||
}
|
||||
e := &supervisor.StartTask{}
|
||||
e.ID = c.Id
|
||||
e.BundlePath = c.BundlePath
|
||||
e.Stdin = c.Stdin
|
||||
e.Stdout = c.Stdout
|
||||
e.Stderr = c.Stderr
|
||||
e.Labels = c.Labels
|
||||
e.NoPivotRoot = c.NoPivotRoot
|
||||
e.Runtime = c.Runtime
|
||||
e.RuntimeArgs = c.RuntimeArgs
|
||||
e.StartResponse = make(chan supervisor.StartResponse, 1)
|
||||
if c.Checkpoint != "" {
|
||||
e.CheckpointDir = c.CheckpointDir
|
||||
e.Checkpoint = &runtime.Checkpoint{
|
||||
Name: c.Checkpoint,
|
||||
}
|
||||
}
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := <-e.StartResponse
|
||||
apiC, err := createAPIContainer(r.Container, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.CreateContainerResponse{
|
||||
Container: apiC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) CreateCheckpoint(ctx context.Context, r *types.CreateCheckpointRequest) (*types.CreateCheckpointResponse, error) {
|
||||
e := &supervisor.CreateCheckpointTask{}
|
||||
e.ID = r.Id
|
||||
e.CheckpointDir = r.CheckpointDir
|
||||
e.Checkpoint = &runtime.Checkpoint{
|
||||
Name: r.Checkpoint.Name,
|
||||
Exit: r.Checkpoint.Exit,
|
||||
TCP: r.Checkpoint.Tcp,
|
||||
UnixSockets: r.Checkpoint.UnixSockets,
|
||||
Shell: r.Checkpoint.Shell,
|
||||
EmptyNS: r.Checkpoint.EmptyNS,
|
||||
}
|
||||
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.CreateCheckpointResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) DeleteCheckpoint(ctx context.Context, r *types.DeleteCheckpointRequest) (*types.DeleteCheckpointResponse, error) {
|
||||
if r.Name == "" {
|
||||
return nil, errors.New("checkpoint name cannot be empty")
|
||||
}
|
||||
e := &supervisor.DeleteCheckpointTask{}
|
||||
e.ID = r.Id
|
||||
e.CheckpointDir = r.CheckpointDir
|
||||
e.Checkpoint = &runtime.Checkpoint{
|
||||
Name: r.Name,
|
||||
}
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.DeleteCheckpointResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) ListCheckpoint(ctx context.Context, r *types.ListCheckpointRequest) (*types.ListCheckpointResponse, error) {
|
||||
e := &supervisor.GetContainersTask{}
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var container runtime.Container
|
||||
for _, c := range e.Containers {
|
||||
if c.ID() == r.Id {
|
||||
container = c
|
||||
break
|
||||
}
|
||||
}
|
||||
if container == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "no such containers")
|
||||
}
|
||||
var out []*types.Checkpoint
|
||||
checkpoints, err := container.Checkpoints(r.CheckpointDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range checkpoints {
|
||||
out = append(out, &types.Checkpoint{
|
||||
Name: c.Name,
|
||||
Tcp: c.TCP,
|
||||
Shell: c.Shell,
|
||||
UnixSockets: c.UnixSockets,
|
||||
// TODO: figure out timestamp
|
||||
//Timestamp: c.Timestamp,
|
||||
})
|
||||
}
|
||||
return &types.ListCheckpointResponse{Checkpoints: out}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) Signal(ctx context.Context, r *types.SignalRequest) (*types.SignalResponse, error) {
|
||||
e := &supervisor.SignalTask{}
|
||||
e.ID = r.Id
|
||||
e.PID = r.Pid
|
||||
e.Signal = syscall.Signal(int(r.Signal))
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.SignalResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) State(ctx context.Context, r *types.StateRequest) (*types.StateResponse, error) {
|
||||
|
||||
getState := func(c runtime.Container) (interface{}, error) {
|
||||
return createAPIContainer(c, true)
|
||||
}
|
||||
|
||||
e := &supervisor.GetContainersTask{}
|
||||
e.ID = r.Id
|
||||
e.GetState = getState
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := s.sv.Machine()
|
||||
state := &types.StateResponse{
|
||||
Machine: &types.Machine{
|
||||
Cpus: uint32(m.Cpus),
|
||||
Memory: uint64(m.Memory),
|
||||
},
|
||||
}
|
||||
for idx := range e.Containers {
|
||||
state.Containers = append(state.Containers, e.States[idx].(*types.Container))
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func createAPIContainer(c runtime.Container, getPids bool) (*types.Container, error) {
|
||||
processes, err := c.Processes()
|
||||
if err != nil {
|
||||
return nil, grpc.Errorf(codes.Internal, "get processes for container: "+err.Error())
|
||||
}
|
||||
var procs []*types.Process
|
||||
for _, p := range processes {
|
||||
oldProc := p.Spec()
|
||||
stdio := p.Stdio()
|
||||
proc := &types.Process{
|
||||
Pid: p.ID(),
|
||||
SystemPid: uint32(p.SystemPid()),
|
||||
Terminal: oldProc.Terminal,
|
||||
Args: oldProc.Args,
|
||||
Env: oldProc.Env,
|
||||
Cwd: oldProc.Cwd,
|
||||
Stdin: stdio.Stdin,
|
||||
Stdout: stdio.Stdout,
|
||||
Stderr: stdio.Stderr,
|
||||
}
|
||||
proc.User = &types.User{
|
||||
Uid: oldProc.User.UID,
|
||||
Gid: oldProc.User.GID,
|
||||
AdditionalGids: oldProc.User.AdditionalGids,
|
||||
}
|
||||
proc.Capabilities = oldProc.Capabilities
|
||||
proc.ApparmorProfile = oldProc.ApparmorProfile
|
||||
proc.SelinuxLabel = oldProc.SelinuxLabel
|
||||
proc.NoNewPrivileges = oldProc.NoNewPrivileges
|
||||
for _, rl := range oldProc.Rlimits {
|
||||
proc.Rlimits = append(proc.Rlimits, &types.Rlimit{
|
||||
Type: rl.Type,
|
||||
Soft: rl.Soft,
|
||||
Hard: rl.Hard,
|
||||
})
|
||||
}
|
||||
procs = append(procs, proc)
|
||||
}
|
||||
var pids []int
|
||||
state := c.State()
|
||||
if getPids && (state == runtime.Running || state == runtime.Paused) {
|
||||
if pids, err = c.Pids(); err != nil {
|
||||
return nil, grpc.Errorf(codes.Internal, "get all pids for container: "+err.Error())
|
||||
}
|
||||
}
|
||||
return &types.Container{
|
||||
Id: c.ID(),
|
||||
BundlePath: c.Path(),
|
||||
Processes: procs,
|
||||
Labels: c.Labels(),
|
||||
Status: string(state),
|
||||
Pids: toUint32(pids),
|
||||
Runtime: c.Runtime(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toUint32(its []int) []uint32 {
|
||||
o := []uint32{}
|
||||
for _, i := range its {
|
||||
o = append(o, uint32(i))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (s *apiServer) UpdateContainer(ctx context.Context, r *types.UpdateContainerRequest) (*types.UpdateContainerResponse, error) {
|
||||
e := &supervisor.UpdateTask{}
|
||||
e.ID = r.Id
|
||||
e.State = runtime.State(r.Status)
|
||||
if r.Resources != nil {
|
||||
rs := r.Resources
|
||||
e.Resources = &runtime.Resource{}
|
||||
if rs.CpuShares != 0 {
|
||||
e.Resources.CPUShares = int64(rs.CpuShares)
|
||||
}
|
||||
if rs.BlkioWeight != 0 {
|
||||
e.Resources.BlkioWeight = uint16(rs.BlkioWeight)
|
||||
}
|
||||
if rs.CpuPeriod != 0 {
|
||||
e.Resources.CPUPeriod = int64(rs.CpuPeriod)
|
||||
}
|
||||
if rs.CpuQuota != 0 {
|
||||
e.Resources.CPUQuota = int64(rs.CpuQuota)
|
||||
}
|
||||
if rs.CpusetCpus != "" {
|
||||
e.Resources.CpusetCpus = rs.CpusetCpus
|
||||
}
|
||||
if rs.CpusetMems != "" {
|
||||
e.Resources.CpusetMems = rs.CpusetMems
|
||||
}
|
||||
if rs.KernelMemoryLimit != 0 {
|
||||
e.Resources.KernelMemory = int64(rs.KernelMemoryLimit)
|
||||
}
|
||||
if rs.KernelTCPMemoryLimit != 0 {
|
||||
e.Resources.KernelTCPMemory = int64(rs.KernelTCPMemoryLimit)
|
||||
}
|
||||
if rs.MemoryLimit != 0 {
|
||||
e.Resources.Memory = int64(rs.MemoryLimit)
|
||||
}
|
||||
if rs.MemoryReservation != 0 {
|
||||
e.Resources.MemoryReservation = int64(rs.MemoryReservation)
|
||||
}
|
||||
if rs.MemorySwap != 0 {
|
||||
e.Resources.MemorySwap = int64(rs.MemorySwap)
|
||||
}
|
||||
}
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.UpdateContainerResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) UpdateProcess(ctx context.Context, r *types.UpdateProcessRequest) (*types.UpdateProcessResponse, error) {
|
||||
e := &supervisor.UpdateProcessTask{}
|
||||
e.ID = r.Id
|
||||
e.PID = r.Pid
|
||||
e.Height = int(r.Height)
|
||||
e.Width = int(r.Width)
|
||||
e.CloseStdin = r.CloseStdin
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.UpdateProcessResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *apiServer) Events(r *types.EventsRequest, stream types.API_EventsServer) error {
|
||||
t := time.Time{}
|
||||
if r.Timestamp != nil {
|
||||
from, err := ptypes.Timestamp(r.Timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t = from
|
||||
}
|
||||
if r.StoredOnly && t.IsZero() {
|
||||
return fmt.Errorf("invalid parameter: StoredOnly cannot be specified without setting a valid Timestamp")
|
||||
}
|
||||
events := s.sv.Events(t, r.StoredOnly, r.Id)
|
||||
defer s.sv.Unsubscribe(events)
|
||||
for e := range events {
|
||||
tsp, err := ptypes.TimestampProto(e.Timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Id == "" || e.ID == r.Id {
|
||||
if err := stream.Send(&types.Event{
|
||||
Id: e.ID,
|
||||
Type: e.Type,
|
||||
Timestamp: tsp,
|
||||
Pid: e.PID,
|
||||
Status: uint32(e.Status),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertToPb(st *runtime.Stat) *types.StatsResponse {
|
||||
tsp, _ := ptypes.TimestampProto(st.Timestamp)
|
||||
pbSt := &types.StatsResponse{
|
||||
Timestamp: tsp,
|
||||
CgroupStats: &types.CgroupStats{},
|
||||
}
|
||||
systemUsage, _ := osutils.GetSystemCPUUsage()
|
||||
pbSt.CgroupStats.CpuStats = &types.CpuStats{
|
||||
CpuUsage: &types.CpuUsage{
|
||||
TotalUsage: st.CPU.Usage.Total,
|
||||
PercpuUsage: st.CPU.Usage.Percpu,
|
||||
UsageInKernelmode: st.CPU.Usage.Kernel,
|
||||
UsageInUsermode: st.CPU.Usage.User,
|
||||
},
|
||||
ThrottlingData: &types.ThrottlingData{
|
||||
Periods: st.CPU.Throttling.Periods,
|
||||
ThrottledPeriods: st.CPU.Throttling.ThrottledPeriods,
|
||||
ThrottledTime: st.CPU.Throttling.ThrottledTime,
|
||||
},
|
||||
SystemUsage: systemUsage,
|
||||
}
|
||||
pbSt.CgroupStats.MemoryStats = &types.MemoryStats{
|
||||
Cache: st.Memory.Cache,
|
||||
Usage: &types.MemoryData{
|
||||
Usage: st.Memory.Usage.Usage,
|
||||
MaxUsage: st.Memory.Usage.Max,
|
||||
Failcnt: st.Memory.Usage.Failcnt,
|
||||
Limit: st.Memory.Usage.Limit,
|
||||
},
|
||||
SwapUsage: &types.MemoryData{
|
||||
Usage: st.Memory.Swap.Usage,
|
||||
MaxUsage: st.Memory.Swap.Max,
|
||||
Failcnt: st.Memory.Swap.Failcnt,
|
||||
Limit: st.Memory.Swap.Limit,
|
||||
},
|
||||
KernelUsage: &types.MemoryData{
|
||||
Usage: st.Memory.Kernel.Usage,
|
||||
MaxUsage: st.Memory.Kernel.Max,
|
||||
Failcnt: st.Memory.Kernel.Failcnt,
|
||||
Limit: st.Memory.Kernel.Limit,
|
||||
},
|
||||
Stats: st.Memory.Raw,
|
||||
}
|
||||
pbSt.CgroupStats.BlkioStats = &types.BlkioStats{
|
||||
IoServiceBytesRecursive: convertBlkioEntryToPb(st.Blkio.IoServiceBytesRecursive),
|
||||
IoServicedRecursive: convertBlkioEntryToPb(st.Blkio.IoServicedRecursive),
|
||||
IoQueuedRecursive: convertBlkioEntryToPb(st.Blkio.IoQueuedRecursive),
|
||||
IoServiceTimeRecursive: convertBlkioEntryToPb(st.Blkio.IoServiceTimeRecursive),
|
||||
IoWaitTimeRecursive: convertBlkioEntryToPb(st.Blkio.IoWaitTimeRecursive),
|
||||
IoMergedRecursive: convertBlkioEntryToPb(st.Blkio.IoMergedRecursive),
|
||||
IoTimeRecursive: convertBlkioEntryToPb(st.Blkio.IoTimeRecursive),
|
||||
SectorsRecursive: convertBlkioEntryToPb(st.Blkio.SectorsRecursive),
|
||||
}
|
||||
pbSt.CgroupStats.HugetlbStats = make(map[string]*types.HugetlbStats)
|
||||
for k, st := range st.Hugetlb {
|
||||
pbSt.CgroupStats.HugetlbStats[k] = &types.HugetlbStats{
|
||||
Usage: st.Usage,
|
||||
MaxUsage: st.Max,
|
||||
Failcnt: st.Failcnt,
|
||||
}
|
||||
}
|
||||
pbSt.CgroupStats.PidsStats = &types.PidsStats{
|
||||
Current: st.Pids.Current,
|
||||
Limit: st.Pids.Limit,
|
||||
}
|
||||
return pbSt
|
||||
}
|
||||
|
||||
func convertBlkioEntryToPb(b []runtime.BlkioEntry) []*types.BlkioStatsEntry {
|
||||
var pbEs []*types.BlkioStatsEntry
|
||||
for _, e := range b {
|
||||
pbEs = append(pbEs, &types.BlkioStatsEntry{
|
||||
Major: e.Major,
|
||||
Minor: e.Minor,
|
||||
Op: e.Op,
|
||||
Value: e.Value,
|
||||
})
|
||||
}
|
||||
return pbEs
|
||||
}
|
||||
|
||||
func (s *apiServer) Stats(ctx context.Context, r *types.StatsRequest) (*types.StatsResponse, error) {
|
||||
e := &supervisor.StatsTask{}
|
||||
e.ID = r.Id
|
||||
e.Stat = make(chan *runtime.Stat, 1)
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats := <-e.Stat
|
||||
t := convertToPb(stats)
|
||||
return t, nil
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/containerd/specs"
|
||||
"github.com/docker/containerd/supervisor"
|
||||
ocs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func (s *apiServer) AddProcess(ctx context.Context, r *types.AddProcessRequest) (*types.AddProcessResponse, error) {
|
||||
process := &specs.ProcessSpec{
|
||||
Terminal: r.Terminal,
|
||||
Args: r.Args,
|
||||
Env: r.Env,
|
||||
Cwd: r.Cwd,
|
||||
}
|
||||
process.User = ocs.User{
|
||||
UID: r.User.Uid,
|
||||
GID: r.User.Gid,
|
||||
AdditionalGids: r.User.AdditionalGids,
|
||||
}
|
||||
process.Capabilities = r.Capabilities
|
||||
process.ApparmorProfile = r.ApparmorProfile
|
||||
process.SelinuxLabel = r.SelinuxLabel
|
||||
process.NoNewPrivileges = r.NoNewPrivileges
|
||||
for _, rl := range r.Rlimits {
|
||||
process.Rlimits = append(process.Rlimits, ocs.Rlimit{
|
||||
Type: rl.Type,
|
||||
Soft: rl.Soft,
|
||||
Hard: rl.Hard,
|
||||
})
|
||||
}
|
||||
if r.Id == "" {
|
||||
return nil, fmt.Errorf("container id cannot be empty")
|
||||
}
|
||||
if r.Pid == "" {
|
||||
return nil, fmt.Errorf("process id cannot be empty")
|
||||
}
|
||||
e := &supervisor.AddProcessTask{}
|
||||
e.ID = r.Id
|
||||
e.PID = r.Pid
|
||||
e.ProcessSpec = process
|
||||
e.Stdin = r.Stdin
|
||||
e.Stdout = r.Stdout
|
||||
e.Stderr = r.Stderr
|
||||
e.StartResponse = make(chan supervisor.StartResponse, 1)
|
||||
s.sv.SendTask(e)
|
||||
if err := <-e.ErrorCh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
<-e.StartResponse
|
||||
return &types.AddProcessResponse{}, nil
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var clockTicksPerSecond uint64
|
||||
|
||||
func (s *apiServer) AddProcess(ctx context.Context, r *types.AddProcessRequest) (*types.AddProcessResponse, error) {
|
||||
return &types.AddProcessResponse{}, errors.New("apiServer AddProcess() not implemented on Solaris")
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,342 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package types;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service API {
|
||||
rpc GetServerVersion(GetServerVersionRequest) returns (GetServerVersionResponse) {}
|
||||
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
|
||||
rpc UpdateContainer(UpdateContainerRequest) returns (UpdateContainerResponse) {}
|
||||
rpc Signal(SignalRequest) returns (SignalResponse) {}
|
||||
rpc UpdateProcess(UpdateProcessRequest) returns (UpdateProcessResponse) {}
|
||||
rpc AddProcess(AddProcessRequest) returns (AddProcessResponse) {}
|
||||
rpc CreateCheckpoint(CreateCheckpointRequest) returns (CreateCheckpointResponse) {}
|
||||
rpc DeleteCheckpoint(DeleteCheckpointRequest) returns (DeleteCheckpointResponse) {}
|
||||
rpc ListCheckpoint(ListCheckpointRequest) returns (ListCheckpointResponse) {}
|
||||
rpc State(StateRequest) returns (StateResponse) {}
|
||||
rpc Events(EventsRequest) returns (stream Event) {}
|
||||
rpc Stats(StatsRequest) returns (StatsResponse) {}
|
||||
}
|
||||
|
||||
message GetServerVersionRequest {
|
||||
}
|
||||
|
||||
message GetServerVersionResponse {
|
||||
uint32 major = 1;
|
||||
uint32 minor = 2;
|
||||
uint32 patch = 3;
|
||||
string revision = 4;
|
||||
}
|
||||
|
||||
message UpdateProcessRequest {
|
||||
string id = 1;
|
||||
string pid = 2;
|
||||
bool closeStdin = 3; // Close stdin of the container
|
||||
uint32 width = 4;
|
||||
uint32 height = 5;
|
||||
}
|
||||
|
||||
message UpdateProcessResponse {
|
||||
}
|
||||
|
||||
message CreateContainerRequest {
|
||||
string id = 1; // ID of container
|
||||
string bundlePath = 2; // path to OCI bundle
|
||||
string checkpoint = 3; // checkpoint name if you want to create immediate checkpoint (optional)
|
||||
string stdin = 4; // path to the file where stdin will be read (optional)
|
||||
string stdout = 5; // path to file where stdout will be written (optional)
|
||||
string stderr = 6; // path to file where stderr will be written (optional)
|
||||
repeated string labels = 7;
|
||||
bool noPivotRoot = 8;
|
||||
string runtime = 9;
|
||||
repeated string runtimeArgs = 10;
|
||||
string checkpointDir = 11; // Directory where checkpoints are stored
|
||||
}
|
||||
|
||||
message CreateContainerResponse {
|
||||
Container container = 1;
|
||||
}
|
||||
|
||||
message SignalRequest {
|
||||
string id = 1; // ID of container
|
||||
string pid = 2; // PID of process inside container
|
||||
uint32 signal = 3; // Signal which will be sent, you can find value in "man 7 signal"
|
||||
}
|
||||
|
||||
message SignalResponse {
|
||||
}
|
||||
|
||||
message AddProcessRequest {
|
||||
string id = 1; // ID of container
|
||||
bool terminal = 2; // Use tty for container stdio
|
||||
User user = 3; // User under which process will be run
|
||||
repeated string args = 4; // Arguments for process, first is binary path itself
|
||||
repeated string env = 5; // List of environment variables for process
|
||||
string cwd = 6; // Workind directory of process
|
||||
string pid = 7; // Process ID
|
||||
string stdin = 8; // path to the file where stdin will be read (optional)
|
||||
string stdout = 9; // path to file where stdout will be written (optional)
|
||||
string stderr = 10; // path to file where stderr will be written (optional)
|
||||
repeated string capabilities = 11;
|
||||
string apparmorProfile = 12;
|
||||
string selinuxLabel = 13;
|
||||
bool noNewPrivileges = 14;
|
||||
repeated Rlimit rlimits = 15;
|
||||
}
|
||||
|
||||
message Rlimit {
|
||||
string type = 1;
|
||||
uint64 soft = 2;
|
||||
uint64 hard = 3;
|
||||
}
|
||||
|
||||
message User {
|
||||
uint32 uid = 1; // UID of user
|
||||
uint32 gid = 2; // GID of user
|
||||
repeated uint32 additionalGids = 3; // Additional groups to which user will be added
|
||||
}
|
||||
|
||||
message AddProcessResponse {
|
||||
}
|
||||
|
||||
message CreateCheckpointRequest {
|
||||
string id = 1; // ID of container
|
||||
Checkpoint checkpoint = 2; // Checkpoint configuration
|
||||
string checkpointDir = 3; // Directory where checkpoints are stored
|
||||
}
|
||||
|
||||
message CreateCheckpointResponse {
|
||||
}
|
||||
|
||||
message DeleteCheckpointRequest {
|
||||
string id = 1; // ID of container
|
||||
string name = 2; // Name of checkpoint
|
||||
string checkpointDir = 3; // Directory where checkpoints are stored
|
||||
}
|
||||
|
||||
message DeleteCheckpointResponse {
|
||||
}
|
||||
|
||||
message ListCheckpointRequest {
|
||||
string id = 1; // ID of container
|
||||
string checkpointDir = 2; // Directory where checkpoints are stored
|
||||
}
|
||||
|
||||
message Checkpoint {
|
||||
string name = 1; // Name of checkpoint
|
||||
bool exit = 2; // checkpoint configuration: should container exit on checkpoint or not
|
||||
bool tcp = 3; // allow open tcp connections
|
||||
bool unixSockets = 4; // allow external unix sockets
|
||||
bool shell = 5; // allow shell-jobs
|
||||
repeated string emptyNS = 6;
|
||||
}
|
||||
|
||||
message ListCheckpointResponse {
|
||||
repeated Checkpoint checkpoints = 1; // List of checkpoints
|
||||
}
|
||||
|
||||
message StateRequest {
|
||||
string id = 1; // container id for a single container
|
||||
}
|
||||
|
||||
message ContainerState {
|
||||
string status = 1;
|
||||
}
|
||||
|
||||
message Process {
|
||||
string pid = 1;
|
||||
bool terminal = 2; // Use tty for container stdio
|
||||
User user = 3; // User under which process will be run
|
||||
repeated string args = 4; // Arguments for process, first is binary path itself
|
||||
repeated string env = 5; // List of environment variables for process
|
||||
string cwd = 6; // Workind directory of process
|
||||
uint32 systemPid = 7;
|
||||
string stdin = 8; // path to the file where stdin will be read (optional)
|
||||
string stdout = 9; // path to file where stdout will be written (optional)
|
||||
string stderr = 10; // path to file where stderr will be written (optional)
|
||||
repeated string capabilities = 11;
|
||||
string apparmorProfile = 12;
|
||||
string selinuxLabel = 13;
|
||||
bool noNewPrivileges = 14;
|
||||
repeated Rlimit rlimits = 15;
|
||||
}
|
||||
|
||||
message Container {
|
||||
string id = 1; // ID of container
|
||||
string bundlePath = 2; // Path to OCI bundle
|
||||
repeated Process processes = 3; // List of processes which run in container
|
||||
string status = 4; // Container status ("running", "paused", etc.)
|
||||
repeated string labels = 5;
|
||||
repeated uint32 pids = 6;
|
||||
string runtime = 7; // runtime used to execute the container
|
||||
}
|
||||
|
||||
// Machine is information about machine on which containerd is run
|
||||
message Machine {
|
||||
uint32 cpus = 1; // number of cpus
|
||||
uint64 memory = 2; // amount of memory
|
||||
}
|
||||
|
||||
// StateResponse is information about containerd daemon
|
||||
message StateResponse {
|
||||
repeated Container containers = 1;
|
||||
Machine machine = 2;
|
||||
}
|
||||
|
||||
message UpdateContainerRequest {
|
||||
string id = 1; // ID of container
|
||||
string pid = 2;
|
||||
string status = 3; // Status to which containerd will try to change
|
||||
UpdateResource resources =4;
|
||||
}
|
||||
|
||||
message UpdateResource {
|
||||
uint64 blkioWeight = 1;
|
||||
uint64 cpuShares = 2;
|
||||
uint64 cpuPeriod = 3;
|
||||
uint64 cpuQuota = 4;
|
||||
string cpusetCpus = 5;
|
||||
string cpusetMems = 6;
|
||||
uint64 memoryLimit = 7;
|
||||
uint64 memorySwap = 8;
|
||||
uint64 memoryReservation = 9;
|
||||
uint64 kernelMemoryLimit = 10;
|
||||
uint64 kernelTCPMemoryLimit = 11;
|
||||
uint64 blkioLeafWeight = 12;
|
||||
repeated WeightDevice blkioWeightDevice = 13;
|
||||
repeated ThrottleDevice blkioThrottleReadBpsDevice = 14;
|
||||
repeated ThrottleDevice blkioThrottleWriteBpsDevice = 15;
|
||||
repeated ThrottleDevice blkioThrottleReadIopsDevice = 16;
|
||||
repeated ThrottleDevice blkioThrottleWriteIopsDevice = 17;
|
||||
}
|
||||
|
||||
message BlockIODevice {
|
||||
int64 major = 1;
|
||||
int64 minor = 2;
|
||||
}
|
||||
|
||||
message WeightDevice {
|
||||
BlockIODevice blkIODevice = 1;
|
||||
uint32 weight = 2;
|
||||
uint32 leafWeight = 3;
|
||||
}
|
||||
|
||||
message ThrottleDevice {
|
||||
BlockIODevice blkIODevice = 1;
|
||||
uint64 rate = 2;
|
||||
}
|
||||
|
||||
message UpdateContainerResponse {
|
||||
}
|
||||
|
||||
message EventsRequest {
|
||||
// Tag 1 is deprecated (old uint64 timestamp)
|
||||
google.protobuf.Timestamp timestamp = 2;
|
||||
bool storedOnly = 3;
|
||||
string id = 4;
|
||||
}
|
||||
|
||||
message Event {
|
||||
string type = 1;
|
||||
string id = 2;
|
||||
uint32 status = 3;
|
||||
string pid = 4;
|
||||
// Tag 5 is deprecated (old uint64 timestamp)
|
||||
google.protobuf.Timestamp timestamp = 6;
|
||||
}
|
||||
|
||||
message NetworkStats {
|
||||
string name = 1; // name of network interface
|
||||
uint64 rx_bytes = 2;
|
||||
uint64 rx_Packets = 3;
|
||||
uint64 Rx_errors = 4;
|
||||
uint64 Rx_dropped = 5;
|
||||
uint64 Tx_bytes = 6;
|
||||
uint64 Tx_packets = 7;
|
||||
uint64 Tx_errors = 8;
|
||||
uint64 Tx_dropped = 9;
|
||||
}
|
||||
|
||||
message CpuUsage {
|
||||
uint64 total_usage = 1;
|
||||
repeated uint64 percpu_usage = 2;
|
||||
uint64 usage_in_kernelmode = 3;
|
||||
uint64 usage_in_usermode = 4;
|
||||
}
|
||||
|
||||
message ThrottlingData {
|
||||
uint64 periods = 1;
|
||||
uint64 throttled_periods = 2;
|
||||
uint64 throttled_time = 3;
|
||||
}
|
||||
|
||||
message CpuStats {
|
||||
CpuUsage cpu_usage = 1;
|
||||
ThrottlingData throttling_data = 2;
|
||||
uint64 system_usage = 3;
|
||||
}
|
||||
|
||||
message PidsStats {
|
||||
uint64 current = 1;
|
||||
uint64 limit = 2;
|
||||
}
|
||||
|
||||
message MemoryData {
|
||||
uint64 usage = 1;
|
||||
uint64 max_usage = 2;
|
||||
uint64 failcnt = 3;
|
||||
uint64 limit = 4;
|
||||
}
|
||||
|
||||
message MemoryStats {
|
||||
uint64 cache = 1;
|
||||
MemoryData usage = 2;
|
||||
MemoryData swap_usage = 3;
|
||||
MemoryData kernel_usage = 4;
|
||||
map<string, uint64> stats = 5;
|
||||
}
|
||||
|
||||
message BlkioStatsEntry {
|
||||
uint64 major = 1;
|
||||
uint64 minor = 2;
|
||||
string op = 3;
|
||||
uint64 value = 4;
|
||||
}
|
||||
|
||||
message BlkioStats {
|
||||
repeated BlkioStatsEntry io_service_bytes_recursive = 1; // number of bytes transferred to and from the block device
|
||||
repeated BlkioStatsEntry io_serviced_recursive = 2;
|
||||
repeated BlkioStatsEntry io_queued_recursive = 3;
|
||||
repeated BlkioStatsEntry io_service_time_recursive = 4;
|
||||
repeated BlkioStatsEntry io_wait_time_recursive = 5;
|
||||
repeated BlkioStatsEntry io_merged_recursive = 6;
|
||||
repeated BlkioStatsEntry io_time_recursive = 7;
|
||||
repeated BlkioStatsEntry sectors_recursive = 8;
|
||||
}
|
||||
|
||||
message HugetlbStats {
|
||||
uint64 usage = 1;
|
||||
uint64 max_usage = 2;
|
||||
uint64 failcnt = 3;
|
||||
uint64 limit = 4;
|
||||
}
|
||||
|
||||
message CgroupStats {
|
||||
CpuStats cpu_stats = 1;
|
||||
MemoryStats memory_stats = 2;
|
||||
BlkioStats blkio_stats = 3;
|
||||
map<string, HugetlbStats> hugetlb_stats = 4; // the map is in the format "size of hugepage: stats of the hugepage"
|
||||
PidsStats pids_stats = 5;
|
||||
}
|
||||
|
||||
message StatsResponse {
|
||||
repeated NetworkStats network_stats = 1;
|
||||
CgroupStats cgroup_stats = 2;
|
||||
// Tag 3 is deprecated (old uint64 timestamp)
|
||||
google.protobuf.Timestamp timestamp = 4;
|
||||
};
|
||||
|
||||
message StatsRequest {
|
||||
string id = 1;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package image
|
||||
|
||||
//go:generate protoc -I .:../../..:$GOPATH/src --gogo_out=plugins=grpc,import_path=github.com/docker/containerkit/api/image:. image.proto
|
|
@ -1,295 +0,0 @@
|
|||
// Code generated by protoc-gen-gogo.
|
||||
// source: image.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package image is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
image.proto
|
||||
|
||||
It has these top-level messages:
|
||||
PrepareRequest
|
||||
PrepareResponse
|
||||
CleanupRequest
|
||||
CleanupResponse
|
||||
CommitRequest
|
||||
CommitResponse
|
||||
*/
|
||||
package image
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
import docker_containerkit_types "github.com/docker/containerkit/api/types/mount"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
const _ = proto.GoGoProtoPackageIsVersion1
|
||||
|
||||
type PrepareRequest struct {
|
||||
// Path specifies the filesystem path to target for the image preparation.
|
||||
//
|
||||
// These will influence the values of "target" in the emitted mounts. It
|
||||
// must be unique per usage of the prepared mount and can only be prepared
|
||||
// again after a call to cleanup.
|
||||
Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
|
||||
// name of the image to prepare.
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PrepareRequest) Reset() { *m = PrepareRequest{} }
|
||||
func (m *PrepareRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PrepareRequest) ProtoMessage() {}
|
||||
func (*PrepareRequest) Descriptor() ([]byte, []int) { return fileDescriptorImage, []int{0} }
|
||||
|
||||
type PrepareResponse struct {
|
||||
// Layers provides a list of mounts to use with container creation. The
|
||||
// layers will be mounted, in order, assembling the root filesystem.
|
||||
//
|
||||
// Typically, these can be augmented with other mounts from the volume
|
||||
// service, tmpfs, application-specific bind mounts or even mounts from
|
||||
// other containers.
|
||||
Layers []*docker_containerkit_types.Mount `protobuf:"bytes,1,rep,name=layers" json:"layers,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PrepareResponse) Reset() { *m = PrepareResponse{} }
|
||||
func (m *PrepareResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*PrepareResponse) ProtoMessage() {}
|
||||
func (*PrepareResponse) Descriptor() ([]byte, []int) { return fileDescriptorImage, []int{1} }
|
||||
|
||||
func (m *PrepareResponse) GetLayers() []*docker_containerkit_types.Mount {
|
||||
if m != nil {
|
||||
return m.Layers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CleanupRequest struct {
|
||||
// Path cleans up the path used for the image.
|
||||
// ID identifies the prepared image to cleanup.
|
||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CleanupRequest) Reset() { *m = CleanupRequest{} }
|
||||
func (m *CleanupRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*CleanupRequest) ProtoMessage() {}
|
||||
func (*CleanupRequest) Descriptor() ([]byte, []int) { return fileDescriptorImage, []int{2} }
|
||||
|
||||
type CleanupResponse struct {
|
||||
}
|
||||
|
||||
func (m *CleanupResponse) Reset() { *m = CleanupResponse{} }
|
||||
func (m *CleanupResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*CleanupResponse) ProtoMessage() {}
|
||||
func (*CleanupResponse) Descriptor() ([]byte, []int) { return fileDescriptorImage, []int{3} }
|
||||
|
||||
// CommitRequest provides argument for the Commit RPC.
|
||||
type CommitRequest struct {
|
||||
// Path to a prepared image to capture changes.
|
||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CommitRequest) Reset() { *m = CommitRequest{} }
|
||||
func (m *CommitRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*CommitRequest) ProtoMessage() {}
|
||||
func (*CommitRequest) Descriptor() ([]byte, []int) { return fileDescriptorImage, []int{4} }
|
||||
|
||||
type CommitResponse struct {
|
||||
// name identifies the entity created as part of the image.
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CommitResponse) Reset() { *m = CommitResponse{} }
|
||||
func (m *CommitResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*CommitResponse) ProtoMessage() {}
|
||||
func (*CommitResponse) Descriptor() ([]byte, []int) { return fileDescriptorImage, []int{5} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*PrepareRequest)(nil), "docker.containerkit.types.PrepareRequest")
|
||||
proto.RegisterType((*PrepareResponse)(nil), "docker.containerkit.types.PrepareResponse")
|
||||
proto.RegisterType((*CleanupRequest)(nil), "docker.containerkit.types.CleanupRequest")
|
||||
proto.RegisterType((*CleanupResponse)(nil), "docker.containerkit.types.CleanupResponse")
|
||||
proto.RegisterType((*CommitRequest)(nil), "docker.containerkit.types.CommitRequest")
|
||||
proto.RegisterType((*CommitResponse)(nil), "docker.containerkit.types.CommitResponse")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion2
|
||||
|
||||
// Client API for Images service
|
||||
|
||||
type ImagesClient interface {
|
||||
// Prepare declares that an image is required for use. A prepared image,
|
||||
// complete with a set of mounts to use for the image will be provided.
|
||||
Prepare(ctx context.Context, in *PrepareRequest, opts ...grpc.CallOption) (*PrepareResponse, error)
|
||||
// Cleanup instructs the images service to cleanup resources for the image.
|
||||
Cleanup(ctx context.Context, in *CleanupRequest, opts ...grpc.CallOption) (*CleanupResponse, error)
|
||||
// Commit
|
||||
Commit(ctx context.Context, in *CommitRequest, opts ...grpc.CallOption) (*CommitResponse, error)
|
||||
}
|
||||
|
||||
type imagesClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewImagesClient(cc *grpc.ClientConn) ImagesClient {
|
||||
return &imagesClient{cc}
|
||||
}
|
||||
|
||||
func (c *imagesClient) Prepare(ctx context.Context, in *PrepareRequest, opts ...grpc.CallOption) (*PrepareResponse, error) {
|
||||
out := new(PrepareResponse)
|
||||
err := grpc.Invoke(ctx, "/docker.containerkit.types.Images/Prepare", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *imagesClient) Cleanup(ctx context.Context, in *CleanupRequest, opts ...grpc.CallOption) (*CleanupResponse, error) {
|
||||
out := new(CleanupResponse)
|
||||
err := grpc.Invoke(ctx, "/docker.containerkit.types.Images/Cleanup", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *imagesClient) Commit(ctx context.Context, in *CommitRequest, opts ...grpc.CallOption) (*CommitResponse, error) {
|
||||
out := new(CommitResponse)
|
||||
err := grpc.Invoke(ctx, "/docker.containerkit.types.Images/Commit", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Images service
|
||||
|
||||
type ImagesServer interface {
|
||||
// Prepare declares that an image is required for use. A prepared image,
|
||||
// complete with a set of mounts to use for the image will be provided.
|
||||
Prepare(context.Context, *PrepareRequest) (*PrepareResponse, error)
|
||||
// Cleanup instructs the images service to cleanup resources for the image.
|
||||
Cleanup(context.Context, *CleanupRequest) (*CleanupResponse, error)
|
||||
// Commit
|
||||
Commit(context.Context, *CommitRequest) (*CommitResponse, error)
|
||||
}
|
||||
|
||||
func RegisterImagesServer(s *grpc.Server, srv ImagesServer) {
|
||||
s.RegisterService(&_Images_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Images_Prepare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PrepareRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ImagesServer).Prepare(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/docker.containerkit.types.Images/Prepare",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ImagesServer).Prepare(ctx, req.(*PrepareRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Images_Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CleanupRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ImagesServer).Cleanup(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/docker.containerkit.types.Images/Cleanup",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ImagesServer).Cleanup(ctx, req.(*CleanupRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Images_Commit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CommitRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ImagesServer).Commit(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/docker.containerkit.types.Images/Commit",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ImagesServer).Commit(ctx, req.(*CommitRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Images_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "docker.containerkit.types.Images",
|
||||
HandlerType: (*ImagesServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Prepare",
|
||||
Handler: _Images_Prepare_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Cleanup",
|
||||
Handler: _Images_Cleanup_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Commit",
|
||||
Handler: _Images_Commit_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
}
|
||||
|
||||
var fileDescriptorImage = []byte{
|
||||
// 314 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x52, 0x4f, 0x6b, 0x3a, 0x31,
|
||||
0x14, 0x64, 0xf5, 0xc7, 0xca, 0xef, 0x49, 0x95, 0xe6, 0x64, 0xf7, 0x24, 0xdb, 0x1e, 0xb4, 0xd0,
|
||||
0x0d, 0xd8, 0x8b, 0xf4, 0xea, 0xa9, 0x94, 0x42, 0xf1, 0x5e, 0x30, 0xda, 0xd7, 0x35, 0x68, 0xfe,
|
||||
0x34, 0xc9, 0x1e, 0xfc, 0x4c, 0xfd, 0x92, 0x65, 0x93, 0xd4, 0x7f, 0xd0, 0xc5, 0xcb, 0xf2, 0x12,
|
||||
0x66, 0xe6, 0xcd, 0xcc, 0x06, 0xba, 0x5c, 0xb0, 0x12, 0x0b, 0x6d, 0x94, 0x53, 0xe4, 0xe6, 0x43,
|
||||
0xad, 0x36, 0x68, 0x8a, 0x95, 0x92, 0x8e, 0x71, 0x89, 0x66, 0xc3, 0x5d, 0xe1, 0x76, 0x1a, 0x6d,
|
||||
0xf6, 0x50, 0x72, 0xb7, 0xae, 0x96, 0xc5, 0x4a, 0x09, 0x5a, 0xaa, 0x52, 0x51, 0xcf, 0x58, 0x56,
|
||||
0x9f, 0xfe, 0xe4, 0x0f, 0x7e, 0x0a, 0x4a, 0xd9, 0xd3, 0x11, 0x3c, 0x88, 0xd2, 0x63, 0x51, 0xca,
|
||||
0x34, 0xa7, 0x5e, 0x98, 0x0a, 0x55, 0x49, 0x17, 0xbe, 0x81, 0x9b, 0x4f, 0xa1, 0xf7, 0x66, 0x50,
|
||||
0x33, 0x83, 0x73, 0xfc, 0xaa, 0xd0, 0x3a, 0x42, 0xe0, 0x9f, 0x66, 0x6e, 0x3d, 0x68, 0x0f, 0x93,
|
||||
0xd1, 0xff, 0xb9, 0x9f, 0xeb, 0x3b, 0xc9, 0x04, 0x0e, 0x5a, 0xe1, 0xae, 0x9e, 0xf3, 0x17, 0xe8,
|
||||
0xef, 0x99, 0x56, 0x2b, 0x69, 0x91, 0x4c, 0x21, 0xdd, 0xb2, 0x1d, 0x1a, 0x3b, 0x48, 0x86, 0xed,
|
||||
0x51, 0x77, 0x32, 0x2c, 0xfe, 0xcc, 0x58, 0xbc, 0xd6, 0x26, 0xe6, 0x11, 0x9f, 0xdf, 0x41, 0x6f,
|
||||
0xb6, 0x45, 0x26, 0x2b, 0x7d, 0x6e, 0x23, 0x39, 0xd8, 0xc8, 0xaf, 0xa1, 0xbf, 0x47, 0x85, 0x95,
|
||||
0xf9, 0x2d, 0x5c, 0xcd, 0x94, 0x10, 0xdc, 0x35, 0xf1, 0x6a, 0xf5, 0x08, 0x8a, 0x4e, 0x7f, 0x03,
|
||||
0x25, 0x87, 0x40, 0x93, 0xef, 0x16, 0xa4, 0xcf, 0xf5, 0x0f, 0xb2, 0x64, 0x01, 0x9d, 0x98, 0x8d,
|
||||
0x8c, 0x1b, 0x32, 0x9c, 0x36, 0x97, 0xdd, 0x5f, 0x02, 0x8d, 0x06, 0x16, 0xd0, 0x89, 0x51, 0x1a,
|
||||
0x37, 0x9c, 0x96, 0xd2, 0xb8, 0xe1, 0xac, 0x19, 0xf2, 0x0e, 0x69, 0x08, 0x4d, 0x46, 0x4d, 0xac,
|
||||
0xe3, 0xf2, 0xb2, 0xf1, 0x05, 0xc8, 0x20, 0xbf, 0x4c, 0xfd, 0xfb, 0x79, 0xfc, 0x09, 0x00, 0x00,
|
||||
0xff, 0xff, 0x28, 0x48, 0xab, 0xcd, 0xd4, 0x02, 0x00, 0x00,
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package docker.containerkit.types;
|
||||
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
import "github.com/docker/containerkit/api/types/mount/mount.proto";
|
||||
|
||||
// Images abstracts the graph driver and image store.
|
||||
//
|
||||
// The interface is to be able to request and manage images. The output,
|
||||
// provided as part of the Prepare step, is to expose a set of mounts that can
|
||||
// be used at container startup to run the image.
|
||||
service Images {
|
||||
// Prepare declares that an image is required for use. A prepared image,
|
||||
// complete with a set of mounts to use for the image will be provided.
|
||||
rpc Prepare(PrepareRequest) returns (PrepareResponse);
|
||||
|
||||
// Cleanup instructs the images service to cleanup resources for the image.
|
||||
rpc Cleanup(CleanupRequest) returns (CleanupResponse);
|
||||
|
||||
// Commit
|
||||
rpc Commit(CommitRequest) returns (CommitResponse);
|
||||
|
||||
// NOTE(stevvooe): Placeholders for other operations here. Consider
|
||||
// splitting this into a graphdriver like service (Prepare/Cleanup) and an
|
||||
// image store service.
|
||||
//
|
||||
// Really, we want to separate image identification from CAS
|
||||
// identification, so placing push/pull here may cause too much coupling.
|
||||
// It might better to be able to import the layers here in the same way the
|
||||
// graphdriver works, then only use the image metadata to maintain the link
|
||||
// here.
|
||||
//
|
||||
// Basically, we want to avoid the tight coupling present between the image
|
||||
// store and graphdriver in docker today.
|
||||
//
|
||||
// rpc Push(PushRequest) returns (stream PullRequest);
|
||||
// rpc Pull(PullRequest) returns (stream PullResponse);
|
||||
}
|
||||
|
||||
message PrepareRequest {
|
||||
// Path specifies the filesystem path to target for the image preparation.
|
||||
//
|
||||
// These will influence the values of "target" in the emitted mounts. It
|
||||
// must be unique per usage of the prepared mount and can only be prepared
|
||||
// again after a call to cleanup.
|
||||
string path = 3;
|
||||
|
||||
// name of the image to prepare.
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message PrepareResponse {
|
||||
// Layers provides a list of mounts to use with container creation. The
|
||||
// layers will be mounted, in order, assembling the root filesystem.
|
||||
//
|
||||
// Typically, these can be augmented with other mounts from the volume
|
||||
// service, tmpfs, application-specific bind mounts or even mounts from
|
||||
// other containers.
|
||||
repeated types.Mount layers = 1;
|
||||
|
||||
// TODO(stevvooe): It is unclear whether or not we should integrate image
|
||||
// metadata with this part of the service.
|
||||
}
|
||||
|
||||
message CleanupRequest {
|
||||
// Path cleans up the path used for the image.
|
||||
// ID identifies the prepared image to cleanup.
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message CleanupResponse { }
|
||||
|
||||
// CommitRequest provides argument for the Commit RPC.
|
||||
message CommitRequest {
|
||||
// Path to a prepared image to capture changes.
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message CommitResponse {
|
||||
// name identifies the entity created as part of the image.
|
||||
string name = 1;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package pprof
|
||||
|
||||
import (
|
||||
// expvar init routine adds the "/debug/vars" handler
|
||||
_ "expvar"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
// New returns a new handler serving pprof information
|
||||
func New() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/pprof/block", pprof.Handler("block"))
|
||||
mux.Handle("/pprof/heap", pprof.Handler("heap"))
|
||||
mux.Handle("/pprof/goroutine", pprof.Handler("goroutine"))
|
||||
mux.Handle("/pprof/threadcreate", pprof.Handler("threadcreate"))
|
||||
return mux
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package mount
|
||||
|
||||
//go:generate protoc -I .:../../..:$GOPATH/src --gogo_out=plugins=grpc,import_path=github.com/docker/containerkit/api/types/mount:. mount.proto
|
||||
|
||||
//+++go:generate protoc -I .:../../..:$GOPATH/src --gogo_out=plugins=grpc,import_path=github.com/docker/containerkit/api/types/mount,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. mount.proto
|
|
@ -1,71 +0,0 @@
|
|||
// Code generated by protoc-gen-gogo.
|
||||
// source: mount.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package mount is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
mount.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Mount
|
||||
*/
|
||||
package mount
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
const _ = proto.GoGoProtoPackageIsVersion1
|
||||
|
||||
// Mount describes mounts for a container.
|
||||
//
|
||||
// This type is the lingua franca of ContainerKit. All services provide mounts
|
||||
// to be used with the container at creation time.
|
||||
//
|
||||
// The Mount type follows the structure of the mount syscall, including a type,
|
||||
// source, target and options.
|
||||
type Mount struct {
|
||||
// Type defines the nature of the mount.
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
// Source specifies the name of the mount. Depending on mount type, this
|
||||
// may be a volume name or a host path, or even ignored.
|
||||
Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"`
|
||||
// Target path in container
|
||||
Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"`
|
||||
// Options specifies zero or more fstab style mount options.
|
||||
Options []string `protobuf:"bytes,4,rep,name=options" json:"options,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Mount) Reset() { *m = Mount{} }
|
||||
func (m *Mount) String() string { return proto.CompactTextString(m) }
|
||||
func (*Mount) ProtoMessage() {}
|
||||
func (*Mount) Descriptor() ([]byte, []int) { return fileDescriptorMount, []int{0} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Mount)(nil), "docker.containerkit.types.Mount")
|
||||
}
|
||||
|
||||
var fileDescriptorMount = []byte{
|
||||
// 163 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x34, 0x8d, 0xb1, 0x0e, 0x82, 0x30,
|
||||
0x10, 0x86, 0x83, 0x20, 0x86, 0xba, 0x75, 0x30, 0xd5, 0x89, 0x38, 0xb1, 0x58, 0x06, 0x9f, 0xc3,
|
||||
0x85, 0x37, 0x80, 0x7a, 0xd6, 0x86, 0xd0, 0x23, 0xe5, 0x3a, 0xf8, 0xf6, 0xa6, 0x57, 0xdd, 0xfe,
|
||||
0xef, 0x4b, 0xee, 0x3b, 0x71, 0x5c, 0x30, 0x7a, 0xd2, 0x6b, 0x40, 0x42, 0x79, 0x7e, 0xa2, 0x99,
|
||||
0x21, 0x68, 0x83, 0x9e, 0x46, 0xe7, 0x21, 0xcc, 0x8e, 0x34, 0x7d, 0x56, 0xd8, 0x2e, 0x37, 0xeb,
|
||||
0xe8, 0x1d, 0x27, 0x6d, 0x70, 0xe9, 0x2d, 0x5a, 0xec, 0xf9, 0x62, 0x8a, 0x2f, 0x26, 0x06, 0x5e,
|
||||
0xb9, 0x74, 0x05, 0xb1, 0x7f, 0xa4, 0xb0, 0x94, 0xa2, 0x4a, 0x01, 0x55, 0xb4, 0x45, 0xd7, 0x0c,
|
||||
0xbc, 0xe5, 0x49, 0xd4, 0x1b, 0xc6, 0x60, 0x40, 0xed, 0xd8, 0xfe, 0x28, 0x79, 0x1a, 0x83, 0x05,
|
||||
0x52, 0x65, 0xf6, 0x99, 0xa4, 0x12, 0x07, 0x5c, 0xc9, 0xa1, 0xdf, 0x54, 0xd5, 0x96, 0x5d, 0x33,
|
||||
0xfc, 0x71, 0xaa, 0xf9, 0xdb, 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x20, 0x78, 0xd5, 0x59, 0xc6,
|
||||
0x00, 0x00, 0x00,
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package docker.containerkit.types;
|
||||
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
|
||||
// Mount describes mounts for a container.
|
||||
//
|
||||
// This type is the lingua franca of ContainerKit. All services provide mounts
|
||||
// to be used with the container at creation time.
|
||||
//
|
||||
// The Mount type follows the structure of the mount syscall, including a type,
|
||||
// source, target and options.
|
||||
message Mount {
|
||||
// Type defines the nature of the mount.
|
||||
string type = 1;
|
||||
|
||||
// Source specifies the name of the mount. Depending on mount type, this
|
||||
// may be a volume name or a host path, or even ignored.
|
||||
string source = 2;
|
||||
|
||||
// Target path in container
|
||||
string target = 3;
|
||||
|
||||
// Options specifies zero or more fstab style mount options.
|
||||
repeated string options = 4;
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
// Package types provides several types common to grpc services.
|
||||
package types
|
|
@ -1,161 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/containerd/api/pprof"
|
||||
"github.com/docker/containerd/supervisor"
|
||||
)
|
||||
|
||||
const (
|
||||
usage = `High performance container daemon`
|
||||
minRlimit = 1024
|
||||
defaultStateDir = "/run/containerd"
|
||||
defaultGRPCEndpoint = "unix:///run/containerd/containerd.sock"
|
||||
)
|
||||
|
||||
var daemonFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug output in the logs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "state-dir",
|
||||
Value: defaultStateDir,
|
||||
Usage: "runtime state directory",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "listen,l",
|
||||
Value: defaultGRPCEndpoint,
|
||||
Usage: "proto://address on which the GRPC API will listen",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "runtime,r",
|
||||
Value: "runc",
|
||||
Usage: "name or path of the OCI compliant runtime to use when executing containers",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "runtime-args",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "specify additional runtime args",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "shim",
|
||||
Value: "containerd-shim",
|
||||
Usage: "Name or path of shim",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pprof-address",
|
||||
Usage: "http address to listen for pprof events",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "start-timeout",
|
||||
Value: 15 * time.Second,
|
||||
Usage: "timeout duration for waiting on a container to start before it is killed",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "retain-count",
|
||||
Value: 500,
|
||||
Usage: "number of past events to keep in the event log",
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: time.RFC3339Nano})
|
||||
app := cli.NewApp()
|
||||
app.Name = "containerd"
|
||||
app.Version = getVersion()
|
||||
app.Usage = usage
|
||||
app.Flags = daemonFlags
|
||||
app.Before = func(context *cli.Context) error {
|
||||
if context.GlobalBool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
if p := context.GlobalString("pprof-address"); len(p) > 0 {
|
||||
h := pprof.New()
|
||||
http.Handle("/debug", h)
|
||||
go http.ListenAndServe(p, nil)
|
||||
}
|
||||
if err := checkLimits(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
app.Action = func(context *cli.Context) {
|
||||
if err := daemon(context); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(context *cli.Context) error {
|
||||
signals := make(chan os.Signal, 2048)
|
||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR1)
|
||||
sv, err := supervisor.New(supervisor.Config{
|
||||
StateDir: context.String("state-dir"),
|
||||
Runtime: context.String("runtime"),
|
||||
ShimName: context.String("shim"),
|
||||
RuntimeArgs: context.StringSlice("runtime-args"),
|
||||
Timeout: context.Duration("start-timeout"),
|
||||
EventRetainCount: context.Int("retain-count"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg := &sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
w := supervisor.NewWorker(sv, wg)
|
||||
go w.Start()
|
||||
}
|
||||
if err := sv.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Split the listen string of the form proto://addr
|
||||
var (
|
||||
listenSpec = context.String("listen")
|
||||
listenParts = strings.SplitN(listenSpec, "://", 2)
|
||||
)
|
||||
if len(listenParts) != 2 {
|
||||
return fmt.Errorf("bad listen address format %s, expected proto://address", listenSpec)
|
||||
}
|
||||
server, err := startServer(listenParts[0], listenParts[1], sv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for s := range signals {
|
||||
switch s {
|
||||
case syscall.SIGUSR1:
|
||||
var (
|
||||
buf []byte
|
||||
stackSize int
|
||||
)
|
||||
bufferLen := 16384
|
||||
for stackSize == len(buf) {
|
||||
buf = make([]byte, bufferLen)
|
||||
stackSize = runtime.Stack(buf, true)
|
||||
bufferLen *= 2
|
||||
}
|
||||
buf = buf[:stackSize]
|
||||
logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
|
||||
case syscall.SIGINT, syscall.SIGTERM:
|
||||
logrus.Infof("stopping containerd after receiving %s", s)
|
||||
server.Stop()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/api/grpc/server"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/containerd/supervisor"
|
||||
"github.com/docker/docker/pkg/listeners"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
func startServer(protocol, address string, sv *supervisor.Supervisor) (*grpc.Server, error) {
|
||||
sockets, err := listeners.Init(protocol, address, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(sockets) != 1 {
|
||||
return nil, fmt.Errorf("incorrect number of listeners")
|
||||
}
|
||||
l := sockets[0]
|
||||
s := grpc.NewServer()
|
||||
types.RegisterAPIServer(s, server.NewServer(sv))
|
||||
healthServer := health.NewServer()
|
||||
grpc_health_v1.RegisterHealthServer(s, healthServer)
|
||||
go func() {
|
||||
logrus.Debugf("containerd: grpc api on %s", address)
|
||||
if err := s.Serve(l); err != nil {
|
||||
logrus.WithField("error", err).Fatal("containerd: serve grpc")
|
||||
}
|
||||
}()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// getDefaultID returns the hostname for the instance host
|
||||
func getDefaultID() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hostname
|
||||
}
|
||||
|
||||
func checkLimits() error {
|
||||
var l syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l); err != nil {
|
||||
return err
|
||||
}
|
||||
if l.Cur <= minRlimit {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"current": l.Cur,
|
||||
"max": l.Max,
|
||||
}).Warn("containerd: low RLIMIT_NOFILE changing to max")
|
||||
l.Cur = l.Max
|
||||
return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion() string {
|
||||
if containerd.GitCommit != "" {
|
||||
return fmt.Sprintf("%s commit: %s", containerd.Version, containerd.GitCommit)
|
||||
}
|
||||
return containerd.Version
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
all:
|
||||
go build
|
|
@ -1,165 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
netcontext "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var checkpointSubCmds = []cli.Command{
|
||||
listCheckpointCommand,
|
||||
createCheckpointCommand,
|
||||
deleteCheckpointCommand,
|
||||
}
|
||||
|
||||
var checkpointCommand = cli.Command{
|
||||
Name: "checkpoints",
|
||||
Usage: "list all checkpoints",
|
||||
ArgsUsage: "COMMAND [arguments...]",
|
||||
Subcommands: checkpointSubCmds,
|
||||
Description: func() string {
|
||||
desc := "\n COMMAND:\n"
|
||||
for _, command := range checkpointSubCmds {
|
||||
desc += fmt.Sprintf(" %-10.10s%s\n", command.Name, command.Usage)
|
||||
}
|
||||
return desc
|
||||
}(),
|
||||
Action: listCheckpoints,
|
||||
}
|
||||
|
||||
var listCheckpointCommand = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "list all checkpoints for a container",
|
||||
Action: listCheckpoints,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "checkpoint-dir",
|
||||
Value: "",
|
||||
Usage: "path to checkpoint directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func listCheckpoints(context *cli.Context) {
|
||||
var (
|
||||
c = getClient(context)
|
||||
id = context.Args().First()
|
||||
)
|
||||
if id == "" {
|
||||
fatal("container id cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
resp, err := c.ListCheckpoint(netcontext.Background(), &types.ListCheckpointRequest{
|
||||
Id: id,
|
||||
CheckpointDir: context.String("checkpoint-dir"),
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprint(w, "NAME\tTCP\tUNIX SOCKETS\tSHELL\n")
|
||||
for _, c := range resp.Checkpoints {
|
||||
fmt.Fprintf(w, "%s\t%v\t%v\t%v\n", c.Name, c.Tcp, c.UnixSockets, c.Shell)
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
}
|
||||
|
||||
var createCheckpointCommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create a new checkpoint for the container",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "tcp",
|
||||
Usage: "persist open tcp connections",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "unix-sockets",
|
||||
Usage: "persist unix sockets",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "exit",
|
||||
Usage: "exit the container after the checkpoint completes successfully",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "shell",
|
||||
Usage: "checkpoint shell jobs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "checkpoint-dir",
|
||||
Value: "",
|
||||
Usage: "directory to store checkpoints",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "empty-ns",
|
||||
Usage: "create a namespace, but don't restore its properties",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
var (
|
||||
containerID = context.Args().Get(0)
|
||||
name = context.Args().Get(1)
|
||||
)
|
||||
if containerID == "" {
|
||||
fatal("container id at cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
if name == "" {
|
||||
fatal("checkpoint name cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
c := getClient(context)
|
||||
checkpoint := types.Checkpoint{
|
||||
Name: name,
|
||||
Exit: context.Bool("exit"),
|
||||
Tcp: context.Bool("tcp"),
|
||||
Shell: context.Bool("shell"),
|
||||
UnixSockets: context.Bool("unix-sockets"),
|
||||
}
|
||||
|
||||
emptyNSes := context.StringSlice("empty-ns")
|
||||
checkpoint.EmptyNS = append(checkpoint.EmptyNS, emptyNSes...)
|
||||
|
||||
if _, err := c.CreateCheckpoint(netcontext.Background(), &types.CreateCheckpointRequest{
|
||||
Id: containerID,
|
||||
CheckpointDir: context.String("checkpoint-dir"),
|
||||
Checkpoint: &checkpoint,
|
||||
}); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var deleteCheckpointCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "delete a container's checkpoint",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "checkpoint-dir",
|
||||
Value: "",
|
||||
Usage: "path to checkpoint directory",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
var (
|
||||
containerID = context.Args().Get(0)
|
||||
name = context.Args().Get(1)
|
||||
)
|
||||
if containerID == "" {
|
||||
fatal("container id at cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
if name == "" {
|
||||
fatal("checkpoint name cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
c := getClient(context)
|
||||
if _, err := c.DeleteCheckpoint(netcontext.Background(), &types.DeleteCheckpointRequest{
|
||||
Id: containerID,
|
||||
Name: name,
|
||||
CheckpointDir: context.String("checkpoint-dir"),
|
||||
}); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
},
|
||||
}
|
10
ctr/const.go
10
ctr/const.go
|
@ -1,10 +0,0 @@
|
|||
package main
|
||||
|
||||
// ctr wide constants
|
||||
const (
|
||||
// ExitStatusOK indicates successful completion
|
||||
ExitStatusOK = 0
|
||||
|
||||
// ExitStatusMissingArg indicates failure due to missing argument(s)
|
||||
ExitStatusMissingArg = 1
|
||||
)
|
684
ctr/container.go
684
ctr/container.go
|
@ -1,684 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/containerd/specs"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
netcontext "golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/transport"
|
||||
)
|
||||
|
||||
// TODO: parse flags and pass opts
|
||||
func getClient(ctx *cli.Context) types.APIClient {
|
||||
// Parse proto://address form addresses.
|
||||
bindSpec := ctx.GlobalString("address")
|
||||
bindParts := strings.SplitN(bindSpec, "://", 2)
|
||||
if len(bindParts) != 2 {
|
||||
fatal(fmt.Sprintf("bad bind address format %s, expected proto://address", bindSpec), 1)
|
||||
}
|
||||
|
||||
// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
|
||||
grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
|
||||
dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(ctx.GlobalDuration("conn-timeout"))}
|
||||
dialOpts = append(dialOpts,
|
||||
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return net.DialTimeout(bindParts[0], bindParts[1], timeout)
|
||||
},
|
||||
))
|
||||
conn, err := grpc.Dial(bindSpec, dialOpts...)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
return types.NewAPIClient(conn)
|
||||
}
|
||||
|
||||
var contSubCmds = []cli.Command{
|
||||
execCommand,
|
||||
killCommand,
|
||||
listCommand,
|
||||
pauseCommand,
|
||||
resumeCommand,
|
||||
startCommand,
|
||||
stateCommand,
|
||||
statsCommand,
|
||||
watchCommand,
|
||||
updateCommand,
|
||||
}
|
||||
|
||||
var containersCommand = cli.Command{
|
||||
Name: "containers",
|
||||
Usage: "interact with running containers",
|
||||
ArgsUsage: "COMMAND [arguments...]",
|
||||
Subcommands: contSubCmds,
|
||||
Description: func() string {
|
||||
desc := "\n COMMAND:\n"
|
||||
for _, command := range contSubCmds {
|
||||
desc += fmt.Sprintf(" %-10.10s%s\n", command.Name, command.Usage)
|
||||
}
|
||||
return desc
|
||||
}(),
|
||||
Action: listContainers,
|
||||
}
|
||||
|
||||
var stateCommand = cli.Command{
|
||||
Name: "state",
|
||||
Usage: "get a raw dump of the containerd state",
|
||||
Action: func(context *cli.Context) {
|
||||
c := getClient(context)
|
||||
resp, err := c.State(netcontext.Background(), &types.StateRequest{
|
||||
Id: context.Args().First(),
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
data, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
fmt.Print(string(data))
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "list all running containers",
|
||||
Action: listContainers,
|
||||
}
|
||||
|
||||
func listContainers(context *cli.Context) {
|
||||
c := getClient(context)
|
||||
resp, err := c.State(netcontext.Background(), &types.StateRequest{
|
||||
Id: context.Args().First(),
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprint(w, "ID\tPATH\tSTATUS\tPROCESSES\n")
|
||||
sortContainers(resp.Containers)
|
||||
for _, c := range resp.Containers {
|
||||
procs := []string{}
|
||||
for _, p := range c.Processes {
|
||||
procs = append(procs, p.Pid)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Id, c.BundlePath, c.Status, strings.Join(procs, ","))
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
}
|
||||
|
||||
var startCommand = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "start a container",
|
||||
ArgsUsage: "ID BundlePath",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "checkpoint,c",
|
||||
Value: "",
|
||||
Usage: "checkpoint to start the container from",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "checkpoint-dir",
|
||||
Value: "",
|
||||
Usage: "path to checkpoint directory",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "attach,a",
|
||||
Usage: "connect to the stdio of the container",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "label,l",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "set labels for the container",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "do not use pivot root",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "runtime,r",
|
||||
Value: "runc",
|
||||
Usage: "name or path of the OCI compliant runtime to use when executing containers",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "runtime-args",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "specify additional runtime args",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
var (
|
||||
id = context.Args().Get(0)
|
||||
path = context.Args().Get(1)
|
||||
)
|
||||
if path == "" {
|
||||
fatal("bundle path cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
if id == "" {
|
||||
fatal("container id cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
bpath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
fatal(fmt.Sprintf("cannot get the absolute path of the bundle: %v", err), 1)
|
||||
}
|
||||
s, err := createStdio()
|
||||
defer func() {
|
||||
if s.stdin != "" {
|
||||
os.RemoveAll(filepath.Dir(s.stdin))
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
var (
|
||||
restoreAndCloseStdin func()
|
||||
tty bool
|
||||
c = getClient(context)
|
||||
r = &types.CreateContainerRequest{
|
||||
Id: id,
|
||||
BundlePath: bpath,
|
||||
Checkpoint: context.String("checkpoint"),
|
||||
CheckpointDir: context.String("checkpoint-dir"),
|
||||
Stdin: s.stdin,
|
||||
Stdout: s.stdout,
|
||||
Stderr: s.stderr,
|
||||
Labels: context.StringSlice("label"),
|
||||
NoPivotRoot: context.Bool("no-pivot"),
|
||||
Runtime: context.String("runtime"),
|
||||
RuntimeArgs: context.StringSlice("runtime-args"),
|
||||
}
|
||||
)
|
||||
restoreAndCloseStdin = func() {
|
||||
if state != nil {
|
||||
term.RestoreTerminal(os.Stdin.Fd(), state)
|
||||
}
|
||||
if stdin != nil {
|
||||
stdin.Close()
|
||||
}
|
||||
}
|
||||
defer restoreAndCloseStdin()
|
||||
if context.Bool("attach") {
|
||||
mkterm, err := readTermSetting(bpath)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
tty = mkterm
|
||||
if mkterm {
|
||||
s, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
state = s
|
||||
}
|
||||
if err := attachStdio(s); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
}
|
||||
events, err := c.Events(netcontext.Background(), &types.EventsRequest{})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
if _, err := c.CreateContainer(netcontext.Background(), r); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
if context.Bool("attach") {
|
||||
go func() {
|
||||
io.Copy(stdin, os.Stdin)
|
||||
if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
CloseStdin: true,
|
||||
}); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
restoreAndCloseStdin()
|
||||
}()
|
||||
if tty {
|
||||
resize(id, "init", c)
|
||||
go func() {
|
||||
s := make(chan os.Signal, 64)
|
||||
signal.Notify(s, syscall.SIGWINCH)
|
||||
for range s {
|
||||
if err := resize(id, "init", c); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
waitForExit(c, events, id, "init", restoreAndCloseStdin)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func resize(id, pid string, c types.APIClient) error {
|
||||
ws, err := term.GetWinsize(os.Stdin.Fd())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Width: uint32(ws.Width),
|
||||
Height: uint32(ws.Height),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
stdin io.WriteCloser
|
||||
state *term.State
|
||||
)
|
||||
|
||||
// readTermSetting reads the Terminal option out of the specs configuration
|
||||
// to know if ctr should allocate a pty
|
||||
func readTermSetting(path string) (bool, error) {
|
||||
f, err := os.Open(filepath.Join(path, "config.json"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
var spec specs.Spec
|
||||
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return spec.Process.Terminal, nil
|
||||
}
|
||||
|
||||
func attachStdio(s stdio) error {
|
||||
stdinf, err := os.OpenFile(s.stdin, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: assign to global
|
||||
stdin = stdinf
|
||||
stdoutf, err := os.OpenFile(s.stdout, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(os.Stdout, stdoutf)
|
||||
stderrf, err := os.OpenFile(s.stderr, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(os.Stderr, stderrf)
|
||||
return nil
|
||||
}
|
||||
|
||||
var watchCommand = cli.Command{
|
||||
Name: "watch",
|
||||
Usage: "print container events",
|
||||
Action: func(context *cli.Context) {
|
||||
c := getClient(context)
|
||||
id := context.Args().First()
|
||||
if id != "" {
|
||||
resp, err := c.State(netcontext.Background(), &types.StateRequest{Id: id})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
for _, c := range resp.Containers {
|
||||
if c.Id == id {
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == "" {
|
||||
fatal("Invalid container id", 1)
|
||||
}
|
||||
}
|
||||
events, reqErr := c.Events(netcontext.Background(), &types.EventsRequest{})
|
||||
if reqErr != nil {
|
||||
fatal(reqErr.Error(), 1)
|
||||
}
|
||||
|
||||
for {
|
||||
e, err := events.Recv()
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
|
||||
if id == "" || e.Id == id {
|
||||
fmt.Printf("%#v\n", e)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var pauseCommand = cli.Command{
|
||||
Name: "pause",
|
||||
Usage: "pause a container",
|
||||
Action: func(context *cli.Context) {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
fatal("container id cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
c := getClient(context)
|
||||
_, err := c.UpdateContainer(netcontext.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "paused",
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var resumeCommand = cli.Command{
|
||||
Name: "resume",
|
||||
Usage: "resume a paused container",
|
||||
Action: func(context *cli.Context) {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
fatal("container id cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
c := getClient(context)
|
||||
_, err := c.UpdateContainer(netcontext.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "running",
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var killCommand = cli.Command{
|
||||
Name: "kill",
|
||||
Usage: "send a signal to a container or its processes",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "pid,p",
|
||||
Value: "init",
|
||||
Usage: "pid of the process to signal within the container",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "signal,s",
|
||||
Value: 15,
|
||||
Usage: "signal to send to the container",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
fatal("container id cannot be empty", ExitStatusMissingArg)
|
||||
}
|
||||
c := getClient(context)
|
||||
if _, err := c.Signal(netcontext.Background(), &types.SignalRequest{
|
||||
Id: id,
|
||||
Pid: context.String("pid"),
|
||||
Signal: uint32(context.Int("signal")),
|
||||
}); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var execCommand = cli.Command{
|
||||
Name: "exec",
|
||||
Usage: "exec another process in an existing container",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "container id to add the process to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid",
|
||||
Usage: "process id for the new process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "attach,a",
|
||||
Usage: "connect to the stdio of the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "current working directory for the process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty,t",
|
||||
Usage: "create a terminal for the process",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env,e",
|
||||
Value: &cli.StringSlice{},
|
||||
Usage: "environment variables for the process",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "uid,u",
|
||||
Usage: "user id of the user for the process",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "gid,g",
|
||||
Usage: "group id of the user for the process",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
var restoreAndCloseStdin func()
|
||||
|
||||
p := &types.AddProcessRequest{
|
||||
Id: context.String("id"),
|
||||
Pid: context.String("pid"),
|
||||
Args: context.Args(),
|
||||
Cwd: context.String("cwd"),
|
||||
Terminal: context.Bool("tty"),
|
||||
Env: context.StringSlice("env"),
|
||||
User: &types.User{
|
||||
Uid: uint32(context.Int("uid")),
|
||||
Gid: uint32(context.Int("gid")),
|
||||
},
|
||||
}
|
||||
s, err := createStdio()
|
||||
defer func() {
|
||||
if s.stdin != "" {
|
||||
os.RemoveAll(filepath.Dir(s.stdin))
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
p.Stdin = s.stdin
|
||||
p.Stdout = s.stdout
|
||||
p.Stderr = s.stderr
|
||||
restoreAndCloseStdin = func() {
|
||||
if state != nil {
|
||||
term.RestoreTerminal(os.Stdin.Fd(), state)
|
||||
}
|
||||
if stdin != nil {
|
||||
stdin.Close()
|
||||
}
|
||||
}
|
||||
defer restoreAndCloseStdin()
|
||||
if context.Bool("attach") {
|
||||
if context.Bool("tty") {
|
||||
s, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
state = s
|
||||
}
|
||||
if err := attachStdio(s); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
}
|
||||
c := getClient(context)
|
||||
events, err := c.Events(netcontext.Background(), &types.EventsRequest{})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
if _, err := c.AddProcess(netcontext.Background(), p); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
if context.Bool("attach") {
|
||||
go func() {
|
||||
io.Copy(stdin, os.Stdin)
|
||||
if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{
|
||||
Id: p.Id,
|
||||
Pid: p.Pid,
|
||||
CloseStdin: true,
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
restoreAndCloseStdin()
|
||||
}()
|
||||
if context.Bool("tty") {
|
||||
resize(p.Id, p.Pid, c)
|
||||
go func() {
|
||||
s := make(chan os.Signal, 64)
|
||||
signal.Notify(s, syscall.SIGWINCH)
|
||||
for range s {
|
||||
if err := resize(p.Id, p.Pid, c); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
waitForExit(c, events, context.String("id"), context.String("pid"), restoreAndCloseStdin)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var statsCommand = cli.Command{
|
||||
Name: "stats",
|
||||
Usage: "get stats for running container",
|
||||
Action: func(context *cli.Context) {
|
||||
req := &types.StatsRequest{
|
||||
Id: context.Args().First(),
|
||||
}
|
||||
c := getClient(context)
|
||||
stats, err := c.Stats(netcontext.Background(), req)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
data, err := json.Marshal(stats)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
fmt.Print(string(data))
|
||||
},
|
||||
}
|
||||
|
||||
func getUpdateCommandInt64Flag(context *cli.Context, name string) uint64 {
|
||||
str := context.String(name)
|
||||
if str == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
val, err := strconv.ParseUint(str, 0, 64)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
var updateCommand = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "update a containers resources",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "memory-limit",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "memory-reservation",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "memory-swap",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cpu-quota",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cpu-period",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "kernel-limit",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "kernel-tcp-limit",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "blkio-weight",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cpuset-cpus",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cpuset-mems",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
req := &types.UpdateContainerRequest{
|
||||
Id: context.Args().First(),
|
||||
}
|
||||
req.Resources = &types.UpdateResource{}
|
||||
req.Resources.MemoryLimit = getUpdateCommandInt64Flag(context, "memory-limit")
|
||||
req.Resources.MemoryReservation = getUpdateCommandInt64Flag(context, "memory-reservation")
|
||||
req.Resources.MemorySwap = getUpdateCommandInt64Flag(context, "memory-swap")
|
||||
req.Resources.BlkioWeight = getUpdateCommandInt64Flag(context, "blkio-weight")
|
||||
req.Resources.CpuPeriod = getUpdateCommandInt64Flag(context, "cpu-period")
|
||||
req.Resources.CpuQuota = getUpdateCommandInt64Flag(context, "cpu-quota")
|
||||
req.Resources.CpuShares = getUpdateCommandInt64Flag(context, "cpu-shares")
|
||||
req.Resources.CpusetCpus = context.String("cpuset-cpus")
|
||||
req.Resources.CpusetMems = context.String("cpuset-mems")
|
||||
req.Resources.KernelMemoryLimit = getUpdateCommandInt64Flag(context, "kernel-limit")
|
||||
req.Resources.KernelTCPMemoryLimit = getUpdateCommandInt64Flag(context, "kernel-tcp-limit")
|
||||
c := getClient(context)
|
||||
if _, err := c.UpdateContainer(netcontext.Background(), req); err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func waitForExit(c types.APIClient, events types.API_EventsClient, id, pid string, closer func()) {
|
||||
timestamp := time.Now()
|
||||
for {
|
||||
e, err := events.Recv()
|
||||
if err != nil {
|
||||
if grpc.ErrorDesc(err) == transport.ErrConnClosing.Desc {
|
||||
closer()
|
||||
os.Exit(128 + int(syscall.SIGHUP))
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
tsp, err := ptypes.TimestampProto(timestamp)
|
||||
if err != nil {
|
||||
closer()
|
||||
fmt.Fprintf(os.Stderr, "%s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
events, _ = c.Events(netcontext.Background(), &types.EventsRequest{Timestamp: tsp})
|
||||
continue
|
||||
}
|
||||
timestamp, err = ptypes.Timestamp(e.Timestamp)
|
||||
if e.Id == id && e.Type == "exit" && e.Pid == pid {
|
||||
closer()
|
||||
os.Exit(int(e.Status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type stdio struct {
|
||||
stdin string
|
||||
stdout string
|
||||
stderr string
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func createStdio() (s stdio, err error) {
|
||||
tmp, err := ioutil.TempDir("", "ctr-")
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
// create fifo's for the process
|
||||
for name, fd := range map[string]*string{
|
||||
"stdin": &s.stdin,
|
||||
"stdout": &s.stdout,
|
||||
"stderr": &s.stderr,
|
||||
} {
|
||||
path := filepath.Join(tmp, name)
|
||||
if err := syscall.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return s, err
|
||||
}
|
||||
*fd = path
|
||||
}
|
||||
return s, nil
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func createStdio() (s stdio, err error) {
|
||||
return s, errors.New("createStdio not implemented on Solaris")
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
netcontext "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var eventsCommand = cli.Command{
|
||||
Name: "events",
|
||||
Usage: "receive events from the containerd daemon",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "timestamp,t",
|
||||
Usage: "get events from a specific time stamp in RFC3339Nano format",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
var (
|
||||
t = time.Time{}
|
||||
c = getClient(context)
|
||||
)
|
||||
if ts := context.String("timestamp"); ts != "" {
|
||||
from, err := time.Parse(time.RFC3339Nano, ts)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
t = from
|
||||
}
|
||||
tsp, err := ptypes.TimestampProto(t)
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
events, err := c.Events(netcontext.Background(), &types.EventsRequest{
|
||||
Timestamp: tsp,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 31, 1, 1, ' ', 0)
|
||||
fmt.Fprint(w, "TIME\tTYPE\tID\tPID\tSTATUS\n")
|
||||
w.Flush()
|
||||
for {
|
||||
e, err := events.Recv()
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
t, err := ptypes.Timestamp(e.Timestamp)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to convert timestamp")
|
||||
t = time.Time{}
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", t.Format(time.RFC3339Nano), e.Type, e.Id, e.Pid, e.Status)
|
||||
w.Flush()
|
||||
}
|
||||
},
|
||||
}
|
90
ctr/main.go
90
ctr/main.go
|
@ -1,90 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
netcontext "golang.org/x/net/context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
)
|
||||
|
||||
const usage = `High performance container daemon cli`
|
||||
|
||||
type exit struct {
|
||||
Code int
|
||||
}
|
||||
|
||||
func main() {
|
||||
// We want our defer functions to be run when calling fatal()
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if ex, ok := e.(exit); ok == true {
|
||||
os.Exit(ex.Code)
|
||||
}
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
app := cli.NewApp()
|
||||
app.Name = "ctr"
|
||||
if containerd.GitCommit != "" {
|
||||
app.Version = fmt.Sprintf("%s commit: %s", containerd.Version, containerd.GitCommit)
|
||||
} else {
|
||||
app.Version = containerd.Version
|
||||
}
|
||||
app.Usage = usage
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug output in the logs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "address",
|
||||
Value: "unix:///run/containerd/containerd.sock",
|
||||
Usage: "proto://address of GRPC API",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "conn-timeout",
|
||||
Value: 1 * time.Second,
|
||||
Usage: "GRPC connection timeout",
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
checkpointCommand,
|
||||
containersCommand,
|
||||
eventsCommand,
|
||||
stateCommand,
|
||||
versionCommand,
|
||||
}
|
||||
app.Before = func(context *cli.Context) error {
|
||||
if context.GlobalBool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var versionCommand = cli.Command{
|
||||
Name: "version",
|
||||
Usage: "return the daemon version",
|
||||
Action: func(context *cli.Context) {
|
||||
c := getClient(context)
|
||||
resp, err := c.GetServerVersion(netcontext.Background(), &types.GetServerVersionRequest{})
|
||||
if err != nil {
|
||||
fatal(err.Error(), 1)
|
||||
}
|
||||
fmt.Printf("daemon version %d.%d.%d commit: %s\n", resp.Major, resp.Minor, resp.Patch, resp.Revision)
|
||||
},
|
||||
}
|
||||
|
||||
func fatal(err string, code int) {
|
||||
fmt.Fprintf(os.Stderr, "[ctr] %s\n", err)
|
||||
panic(exit{code})
|
||||
}
|
27
ctr/sort.go
27
ctr/sort.go
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
)
|
||||
|
||||
func sortContainers(c []*types.Container) {
|
||||
sort.Sort(&containerSorter{c})
|
||||
}
|
||||
|
||||
type containerSorter struct {
|
||||
c []*types.Container
|
||||
}
|
||||
|
||||
func (s *containerSorter) Len() int {
|
||||
return len(s.c)
|
||||
}
|
||||
|
||||
func (s *containerSorter) Swap(i, j int) {
|
||||
s.c[i], s.c[j] = s.c[j], s.c[i]
|
||||
}
|
||||
|
||||
func (s *containerSorter) Less(i, j int) bool {
|
||||
return s.c[i].Id < s.c[j].Id
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# API
|
||||
|
||||
The API for containerd is with GRPC over a unix socket located at the default location of `/run/containerd/containerd.sock`.
|
||||
|
||||
At this time please refer to the [proto at](https://github.com/docker/containerd/blob/master/api/grpc/types/api.proto) for the API methods and types.
|
||||
There is a Go implementation and types checked into this repository but alternate language implementations can be created using the grpc and protoc toolchain.
|
|
@ -1,36 +0,0 @@
|
|||
# Attaching to STDIO or TTY
|
||||
|
||||
The model for STDIO, TTY, and logging is a little different in containerd.
|
||||
Because of the various methods that consumers want on the logging side these types of decisions
|
||||
are pushed to the client.
|
||||
Containerd API is developed for access on a single host therefore many things like paths on the host system are acceptable in the API.
|
||||
For the STDIO model the client requesting to start a container provides the paths for the IO.
|
||||
|
||||
## Logging
|
||||
|
||||
If no options are specified on create all STDIO of the processes launched by containerd will be sent to `/dev/null`.
|
||||
If you want containerd to send the STDIO of the processes to a file, you can pass paths to the files in the create container method defined by this proto in the stdin, stdout, and stderr fields:
|
||||
|
||||
```proto
|
||||
message CreateContainerRequest {
|
||||
string id = 1; // ID of container
|
||||
string bundlePath = 2; // path to OCI bundle
|
||||
string stdin = 3; // path to the file where stdin will be read (optional)
|
||||
string stdout = 4; // path to file where stdout will be written (optional)
|
||||
string stderr = 5; // path to file where stderr will be written (optional)
|
||||
string console = 6; // path to the console for a container (optional)
|
||||
string checkpoint = 7; // checkpoint name if you want to create immediate checkpoint (optional)
|
||||
}
|
||||
```
|
||||
|
||||
## Attach
|
||||
|
||||
In order to have attach like functionality for your containers you use the same API request but named pipes or fifos can be used to achieve this type of functionality.
|
||||
The default CLI for containerd does this if you specify the `--attach` flag on `create` or `start`.
|
||||
It will create fifos for each of the containers stdio which the CLI can read and write to.
|
||||
This can be used to create an interactive session with the container, `bash` for example, or to have a blocking way to collect the container's STDIO and forward it to your logging facilities.
|
||||
|
||||
## TTY
|
||||
|
||||
The tty model is the same as above only the client creates a pty and provides to other side to containerd in the create request in the `console` field.
|
||||
Containerd will provide the pty to the container to use and the session can be opened with the container after it starts.
|
|
@ -1,12 +0,0 @@
|
|||
# containerd changes to the bundle
|
||||
|
||||
Containerd will make changes to the container's bundle by adding additional files or folders by default with
|
||||
options to change the output.
|
||||
|
||||
The current change that it makes is if you create a checkpoint of a container, the checkpoints will be saved
|
||||
by default in the container bundle at `{bundle}/checkpoints/{checkpoint name}`.
|
||||
A user can also populate this directory and provide the checkpoint name on the create request so that the container is started from this checkpoint.
|
||||
|
||||
|
||||
As of this point, containerd has no other additions to the bundle.
|
||||
Runtime state is currently stored in a tmpfs filesystem like `/run`.
|
208
docs/bundle.md
208
docs/bundle.md
|
@ -1,208 +0,0 @@
|
|||
# Creating OCI bundles
|
||||
|
||||
Since containerd consumes the OCI bundle format containers and configuration will have to be created
|
||||
on the machine that containerd is running on. The easiest way to do this is to download an image
|
||||
with docker and export it.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
First thing we need to do to create a bundle is setup the initial directory structure.
|
||||
Create a directory with a unique name. In this example we will create a redis container.
|
||||
We will create this container in a `/containers` directory.
|
||||
|
||||
|
||||
```bash
|
||||
mkdir redis
|
||||
```
|
||||
|
||||
Inside the `redis` directory create another directory named `rootfs`
|
||||
|
||||
```bash
|
||||
mkdir redis/rootfs
|
||||
```
|
||||
|
||||
## Root Filesystem
|
||||
|
||||
Now we need to populate the `rootfs` directory with the filesystem of a redis container. To do this we
|
||||
need to pull the redis image with docker and export its contents to the `rootfs` directory.
|
||||
|
||||
```bash
|
||||
docker pull redis
|
||||
|
||||
# create the container with a temp name so that we can export it
|
||||
docker create --name tempredis redis
|
||||
|
||||
# export it into the rootfs directory
|
||||
docker export tempredis | tar -C redis/rootfs -xf -
|
||||
|
||||
# remove the container now that we have exported
|
||||
docker rm tempredis
|
||||
```
|
||||
|
||||
Now that we have the root filesystem populated we need to create the configs for the container.
|
||||
|
||||
## Configs
|
||||
|
||||
An easy way to get temp configs for the container bundle is to use the `runc`
|
||||
cli tool from the [runc](https://github.com/opencontainers/runc) repository.
|
||||
|
||||
|
||||
You need to `cd` into the `redis` directory and run the `runc spec` command. After doing this you
|
||||
should have a file `config.json` created. The directory structure should look like this:
|
||||
|
||||
```
|
||||
/containers/redis
|
||||
├── config.json
|
||||
└── rootfs/
|
||||
```
|
||||
|
||||
## Edits
|
||||
|
||||
We need to edit the config to add `redis-server` as the application to launch inside the container,
|
||||
and remove the network namespace so that you can connect to the redis server on your system.
|
||||
The resulting `config.json` should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"ociVersion": "0.4.0",
|
||||
"platform": {
|
||||
"os": "linux",
|
||||
"arch": "amd64"
|
||||
},
|
||||
"process": {
|
||||
"terminal": true,
|
||||
"user": {},
|
||||
"args": [
|
||||
"redis-server", "--bind", "0.0.0.0"
|
||||
],
|
||||
"env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"TERM=xterm"
|
||||
],
|
||||
"cwd": "/",
|
||||
"capabilities": [
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_KILL",
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
],
|
||||
"rlimits": [
|
||||
{
|
||||
"type": "RLIMIT_NOFILE",
|
||||
"hard": 1024,
|
||||
"soft": 1024
|
||||
}
|
||||
],
|
||||
"noNewPrivileges": true
|
||||
},
|
||||
"root": {
|
||||
"path": "rootfs",
|
||||
"readonly": true
|
||||
},
|
||||
"hostname": "runc",
|
||||
"mounts": [
|
||||
{
|
||||
"destination": "/proc",
|
||||
"type": "proc",
|
||||
"source": "proc"
|
||||
},
|
||||
{
|
||||
"destination": "/dev",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"strictatime",
|
||||
"mode=755",
|
||||
"size=65536k"
|
||||
]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/pts",
|
||||
"type": "devpts",
|
||||
"source": "devpts",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"noexec",
|
||||
"newinstance",
|
||||
"ptmxmode=0666",
|
||||
"mode=0620",
|
||||
"gid=5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/shm",
|
||||
"type": "tmpfs",
|
||||
"source": "shm",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"noexec",
|
||||
"nodev",
|
||||
"mode=1777",
|
||||
"size=65536k"
|
||||
]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/mqueue",
|
||||
"type": "mqueue",
|
||||
"source": "mqueue",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"noexec",
|
||||
"nodev"
|
||||
]
|
||||
},
|
||||
{
|
||||
"destination": "/sys",
|
||||
"type": "sysfs",
|
||||
"source": "sysfs",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"noexec",
|
||||
"nodev",
|
||||
"ro"
|
||||
]
|
||||
},
|
||||
{
|
||||
"destination": "/sys/fs/cgroup",
|
||||
"type": "cgroup",
|
||||
"source": "cgroup",
|
||||
"options": [
|
||||
"nosuid",
|
||||
"noexec",
|
||||
"nodev",
|
||||
"relatime",
|
||||
"ro"
|
||||
]
|
||||
}
|
||||
],
|
||||
"hooks": {},
|
||||
"linux": {
|
||||
"resources": {
|
||||
"devices": [
|
||||
{
|
||||
"allow": false,
|
||||
"access": "rwm"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namespaces": [
|
||||
{
|
||||
"type": "pid"
|
||||
},
|
||||
{
|
||||
"type": "ipc"
|
||||
},
|
||||
{
|
||||
"type": "uts"
|
||||
},
|
||||
{
|
||||
"type": "mount"
|
||||
}
|
||||
],
|
||||
"devices": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is what you need to do to make a OCI compliant bundle for containerd to start.
|
159
docs/cli.md
159
docs/cli.md
|
@ -1,159 +0,0 @@
|
|||
# Client CLI
|
||||
|
||||
There is a default cli named `ctr` based on the GRPC api.
|
||||
This cli will allow you to create and manage containers run with containerd.
|
||||
|
||||
```
|
||||
$ ctr -h
|
||||
NAME:
|
||||
ctr - High performance container daemon cli
|
||||
|
||||
USAGE:
|
||||
ctr [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.1.0 commit: 54c213e8a719d734001beb2cb8f130c84cc3bd20
|
||||
|
||||
COMMANDS:
|
||||
checkpoints list all checkpoints
|
||||
containers interact with running containers
|
||||
events receive events from the containerd daemon
|
||||
state get a raw dump of the containerd state
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--debug enable debug output in the logs
|
||||
--address "/run/containerd/containerd.sock" address of GRPC API
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
## Starting a container
|
||||
|
||||
```
|
||||
$ ctr containers start -h
|
||||
NAME:
|
||||
ctr containers start - start a container
|
||||
|
||||
USAGE:
|
||||
ctr containers start [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--checkpoint, -c checkpoint to start the container from
|
||||
--attach, -a connect to the stdio of the container
|
||||
--label, -l [--label option --label option] set labels for the container
|
||||
```
|
||||
|
||||
```bash
|
||||
$ sudo ctr containers start redis /containers/redis
|
||||
```
|
||||
|
||||
`/containers/redis` is the path to an OCI bundle. [See the bundle docs for more information.](bundle.md)
|
||||
|
||||
## Listing containers
|
||||
|
||||
```bash
|
||||
$ sudo ctr containers
|
||||
ID PATH STATUS PROCESSES
|
||||
1 /containers/redis running 14063
|
||||
19 /containers/redis running 14100
|
||||
14 /containers/redis running 14117
|
||||
4 /containers/redis running 14030
|
||||
16 /containers/redis running 14061
|
||||
3 /containers/redis running 14024
|
||||
12 /containers/redis running 14097
|
||||
10 /containers/redis running 14131
|
||||
18 /containers/redis running 13977
|
||||
13 /containers/redis running 13979
|
||||
15 /containers/redis running 13998
|
||||
5 /containers/redis running 14021
|
||||
9 /containers/redis running 14075
|
||||
6 /containers/redis running 14107
|
||||
2 /containers/redis running 14135
|
||||
11 /containers/redis running 13978
|
||||
17 /containers/redis running 13989
|
||||
8 /containers/redis running 14053
|
||||
7 /containers/redis running 14022
|
||||
0 /containers/redis running 14006
|
||||
```
|
||||
|
||||
## Kill a container's process
|
||||
|
||||
```
|
||||
$ ctr containers kill -h
|
||||
NAME:
|
||||
ctr containers kill - send a signal to a container or its processes
|
||||
|
||||
USAGE:
|
||||
ctr containers kill [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--pid, -p "init" pid of the process to signal within the container
|
||||
--signal, -s "15" signal to send to the container
|
||||
```
|
||||
|
||||
## Exec another process into a container
|
||||
|
||||
```
|
||||
$ ctr containers exec -h
|
||||
NAME:
|
||||
ctr containers exec - exec another process in an existing container
|
||||
|
||||
USAGE:
|
||||
ctr containers exec [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--id container id to add the process to
|
||||
--pid process id for the new process
|
||||
--attach, -a connect to the stdio of the container
|
||||
--cwd current working directory for the process
|
||||
--tty, -t create a terminal for the process
|
||||
--env, -e [--env option --env option] environment variables for the process
|
||||
--uid, -u "0" user id of the user for the process
|
||||
--gid, -g "0" group id of the user for the process
|
||||
```
|
||||
|
||||
## Stats for a container
|
||||
|
||||
```
|
||||
$ ctr containers stats -h
|
||||
NAME:
|
||||
ctr containers stats - get stats for running container
|
||||
|
||||
USAGE:
|
||||
ctr containers stats [arguments...]
|
||||
```
|
||||
|
||||
## List checkpoints
|
||||
|
||||
```
|
||||
$ sudo ctr checkpoints redis
|
||||
NAME TCP UNIX SOCKETS SHELL
|
||||
test false false false
|
||||
test2 false false false
|
||||
```
|
||||
|
||||
## Create a new checkpoint
|
||||
|
||||
```
|
||||
$ ctr checkpoints create -h
|
||||
NAME:
|
||||
ctr checkpoints create - create a new checkpoint for the container
|
||||
|
||||
USAGE:
|
||||
ctr checkpoints create [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--tcp persist open tcp connections
|
||||
--unix-sockets perist unix sockets
|
||||
--exit exit the container after the checkpoint completes successfully
|
||||
--shell checkpoint shell jobs
|
||||
```
|
||||
|
||||
## Get events
|
||||
|
||||
```
|
||||
$ sudo ctr events
|
||||
TYPE ID PID STATUS
|
||||
exit redis 24761 0
|
||||
```
|
|
@ -1,27 +0,0 @@
|
|||
# Daemon options
|
||||
|
||||
```
|
||||
$ containerd -h
|
||||
|
||||
NAME:
|
||||
containerd - High performance container daemon
|
||||
|
||||
USAGE:
|
||||
containerd [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.1.0 commit: 54c213e8a719d734001beb2cb8f130c84cc3bd20
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--debug enable debug output in the logs
|
||||
--state-dir "/run/containerd" runtime state directory
|
||||
--metrics-interval "5m0s" interval for flushing metrics to the store
|
||||
--listen, -l "/run/containerd/containerd.sock" Address on which GRPC API will listen
|
||||
--runtime, -r "runc" name of the OCI compliant runtime to use when executing containers
|
||||
--graphite-address Address of graphite server
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
|
@ -1,31 +0,0 @@
|
|||
# Telemetry
|
||||
|
||||
Currently containerd only outputs metrics to stdout but will support dumping to various backends in the future.
|
||||
|
||||
```
|
||||
[containerd] 2015/12/16 11:48:28 timer container-start-time
|
||||
[containerd] 2015/12/16 11:48:28 count: 22
|
||||
[containerd] 2015/12/16 11:48:28 min: 25425883
|
||||
[containerd] 2015/12/16 11:48:28 max: 113077691
|
||||
[containerd] 2015/12/16 11:48:28 mean: 68386923.27
|
||||
[containerd] 2015/12/16 11:48:28 stddev: 20928453.26
|
||||
[containerd] 2015/12/16 11:48:28 median: 65489003.50
|
||||
[containerd] 2015/12/16 11:48:28 75%: 82393210.50
|
||||
[containerd] 2015/12/16 11:48:28 95%: 112267814.75
|
||||
[containerd] 2015/12/16 11:48:28 99%: 113077691.00
|
||||
[containerd] 2015/12/16 11:48:28 99.9%: 113077691.00
|
||||
[containerd] 2015/12/16 11:48:28 1-min rate: 0.00
|
||||
[containerd] 2015/12/16 11:48:28 5-min rate: 0.01
|
||||
[containerd] 2015/12/16 11:48:28 15-min rate: 0.01
|
||||
[containerd] 2015/12/16 11:48:28 mean rate: 0.03
|
||||
[containerd] 2015/12/16 11:48:28 counter containers
|
||||
[containerd] 2015/12/16 11:48:28 count: 1
|
||||
[containerd] 2015/12/16 11:48:28 counter events
|
||||
[containerd] 2015/12/16 11:48:28 count: 87
|
||||
[containerd] 2015/12/16 11:48:28 counter events-subscribers
|
||||
[containerd] 2015/12/16 11:48:28 count: 2
|
||||
[containerd] 2015/12/16 11:48:28 gauge goroutines
|
||||
[containerd] 2015/12/16 11:48:28 value: 38
|
||||
[containerd] 2015/12/16 11:48:28 gauge fds
|
||||
[containerd] 2015/12/16 11:48:28 value: 18
|
||||
```
|
|
@ -1,75 +0,0 @@
|
|||
// single app that will run containers in containerd and output
|
||||
// the total time in seconds that it took for the execution.
|
||||
// go run benchmark.go -count 1000 -bundle /containers/redis
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
netcontext "golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&bundle, "bundle", "/containers/redis", "the bundle path")
|
||||
flag.StringVar(&addr, "addr", "/run/containerd/containerd.sock", "address to the container d instance")
|
||||
flag.IntVar(&count, "count", 1000, "number of containers to run")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
var (
|
||||
count int
|
||||
bundle, addr string
|
||||
group = sync.WaitGroup{}
|
||||
jobs = make(chan string, 20)
|
||||
)
|
||||
|
||||
func getClient() types.APIClient {
|
||||
dialOpts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
dialOpts = append(dialOpts,
|
||||
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return net.DialTimeout("unix", addr, timeout)
|
||||
},
|
||||
))
|
||||
conn, err := grpc.Dial(addr, dialOpts...)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
return types.NewAPIClient(conn)
|
||||
}
|
||||
|
||||
func main() {
|
||||
client := getClient()
|
||||
for i := 0; i < 100; i++ {
|
||||
group.Add(1)
|
||||
go worker(client)
|
||||
}
|
||||
start := time.Now()
|
||||
for i := 0; i < count; i++ {
|
||||
id := strconv.Itoa(i)
|
||||
jobs <- id
|
||||
}
|
||||
close(jobs)
|
||||
group.Wait()
|
||||
end := time.Now()
|
||||
duration := end.Sub(start).Seconds()
|
||||
logrus.Info(duration)
|
||||
}
|
||||
|
||||
func worker(client types.APIClient) {
|
||||
defer group.Done()
|
||||
for id := range jobs {
|
||||
if _, err := client.CreateContainer(netcontext.Background(), &types.CreateContainerRequest{
|
||||
Id: id,
|
||||
BundlePath: bundle,
|
||||
}); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
utils "github.com/docker/containerd/testutils"
|
||||
ocs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
type OciProcessArgs struct {
|
||||
Cmd string
|
||||
Args []string
|
||||
}
|
||||
|
||||
type Bundle struct {
|
||||
Source string
|
||||
Name string
|
||||
Spec ocs.Spec
|
||||
Path string
|
||||
}
|
||||
|
||||
var bundleMap map[string]Bundle
|
||||
|
||||
// untarRootfs untars the given `source` tarPath into `destination/rootfs`
|
||||
func untarRootfs(source string, destination string) error {
|
||||
destination = filepath.Join(destination, "rootfs")
|
||||
if err := os.MkdirAll(destination, 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
tar := exec.Command("tar", "-C", destination, "-xf", source)
|
||||
return tar.Run()
|
||||
}
|
||||
|
||||
// CreateBundleWithFilter generate a new oci-bundle named `name` from
|
||||
// the provide `source` rootfs. It starts from the default spec
|
||||
// generated by `runc spec`, overrides the `spec.Process.Args` value
|
||||
// with `args` and set `spec.Process.Terminal` to false. It then apply
|
||||
// `filter()` to the resulting spec if it is provided.
|
||||
func CreateBundleWithFilter(source, name string, args []string, filter func(spec *ocs.Spec)) error {
|
||||
// Generate the spec
|
||||
var spec ocs.Spec
|
||||
f, err := os.Open(utils.RefOciSpecsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open default spec: %v", err)
|
||||
}
|
||||
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
||||
return fmt.Errorf("Failed to load default spec: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
spec.Process.Args = args
|
||||
spec.Process.Terminal = false
|
||||
if filter != nil {
|
||||
filter(&spec)
|
||||
}
|
||||
|
||||
bundlePath := filepath.Join(utils.BundlesRoot, name)
|
||||
nb := Bundle{source, name, spec, bundlePath}
|
||||
|
||||
// Check that we don't already have such a bundle
|
||||
if b, ok := bundleMap[name]; ok {
|
||||
if reflect.DeepEqual(b, nb) == false {
|
||||
return fmt.Errorf("A bundle name named '%s' already exist but with different properties! %#v != %#v",
|
||||
name, b, nb)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Nothing should be there, but just in case
|
||||
os.RemoveAll(bundlePath)
|
||||
|
||||
if err := untarRootfs(filepath.Join(utils.ArchivesDir, source+".tar"), bundlePath); err != nil {
|
||||
return fmt.Errorf("Failed to untar %s.tar: %v", source, err)
|
||||
}
|
||||
|
||||
// create a place for the io fifo
|
||||
if err := os.Mkdir(filepath.Join(bundlePath, "io"), 0755); err != nil {
|
||||
return fmt.Errorf("Failed to create bundle io directory: %v", err)
|
||||
}
|
||||
|
||||
// Write the updated spec to the right location
|
||||
config, e := os.Create(filepath.Join(bundlePath, "config.json"))
|
||||
if e != nil {
|
||||
return fmt.Errorf("Failed to create oci spec: %v", e)
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
if err := json.NewEncoder(config).Encode(&spec); err != nil {
|
||||
return fmt.Errorf("Failed to encore oci spec: %v", e)
|
||||
}
|
||||
|
||||
bundleMap[name] = nb
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetBundle(name string) *Bundle {
|
||||
bundle, ok := bundleMap[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &bundle
|
||||
}
|
||||
|
||||
func CreateBusyboxBundle(name string, args []string) error {
|
||||
return CreateBundleWithFilter("busybox", name, args, nil)
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
utils "github.com/docker/containerd/testutils"
|
||||
"github.com/go-check/check"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
check.TestingT(t)
|
||||
}
|
||||
|
||||
func init() {
|
||||
check.Suite(&ContainerdSuite{})
|
||||
}
|
||||
|
||||
type ContainerdSuite struct {
|
||||
cwd string
|
||||
outputDir string
|
||||
stateDir string
|
||||
grpcSocket string
|
||||
logFile *os.File
|
||||
cd *exec.Cmd
|
||||
syncChild chan error
|
||||
grpcClient types.APIClient
|
||||
eventFiltersMutex sync.Mutex
|
||||
eventFilters map[string]func(event *types.Event)
|
||||
lastEventTs *timestamp.Timestamp
|
||||
}
|
||||
|
||||
// getClient returns a connection to the Suite containerd
|
||||
func (cs *ContainerdSuite) getClient(socket string) error {
|
||||
// Parse proto://address form addresses.
|
||||
bindParts := strings.SplitN(socket, "://", 2)
|
||||
if len(bindParts) != 2 {
|
||||
return fmt.Errorf("bad bind address format %s, expected proto://address", socket)
|
||||
}
|
||||
|
||||
// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
|
||||
grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
|
||||
dialOpts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
dialOpts = append(dialOpts,
|
||||
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return net.DialTimeout(bindParts[0], bindParts[1], timeout)
|
||||
}),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithTimeout(5*time.Second),
|
||||
)
|
||||
conn, err := grpc.Dial(socket, dialOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
healthClient := grpc_health_v1.NewHealthClient(conn)
|
||||
if _, err := healthClient.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{}); err != nil {
|
||||
return err
|
||||
}
|
||||
cs.grpcClient = types.NewAPIClient(conn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerdEventsHandler will process all events coming from
|
||||
// containerd. If a filter as been register for a given container id
|
||||
// via `SetContainerEventFilter()`, it will be invoked every time an
|
||||
// event for that id is received
|
||||
func (cs *ContainerdSuite) ContainerdEventsHandler(events types.API_EventsClient) {
|
||||
for {
|
||||
e, err := events.Recv()
|
||||
if err != nil {
|
||||
// If daemon died or exited, return
|
||||
if strings.Contains(err.Error(), "transport is closing") {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
events, _ = cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: cs.lastEventTs})
|
||||
continue
|
||||
}
|
||||
cs.lastEventTs = e.Timestamp
|
||||
cs.eventFiltersMutex.Lock()
|
||||
if f, ok := cs.eventFilters[e.Id]; ok {
|
||||
f(e)
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
delete(cs.eventFilters, e.Id)
|
||||
}
|
||||
}
|
||||
cs.eventFiltersMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) StopDaemon(kill bool) {
|
||||
if cs.cd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if kill {
|
||||
cs.cd.Process.Kill()
|
||||
<-cs.syncChild
|
||||
cs.cd = nil
|
||||
} else {
|
||||
// Terminate gently if possible
|
||||
cs.cd.Process.Signal(os.Interrupt)
|
||||
|
||||
done := false
|
||||
for done == false {
|
||||
select {
|
||||
case err := <-cs.syncChild:
|
||||
if err != nil {
|
||||
fmt.Printf("master containerd did not exit cleanly: %v\n", err)
|
||||
}
|
||||
done = true
|
||||
case <-time.After(3 * time.Second):
|
||||
fmt.Println("Timeout while waiting for containerd to exit, killing it!")
|
||||
cs.cd.Process.Kill()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) RestartDaemon(kill bool) error {
|
||||
cs.StopDaemon(kill)
|
||||
|
||||
cd := exec.Command("containerd", "--debug",
|
||||
"--state-dir", cs.stateDir,
|
||||
"--listen", cs.grpcSocket,
|
||||
"--metrics-interval", "0m0s",
|
||||
"--runtime-args", fmt.Sprintf("--root=%s", filepath.Join(cs.cwd, cs.outputDir, "runc")),
|
||||
)
|
||||
cd.Stderr = cs.logFile
|
||||
cd.Stdout = cs.logFile
|
||||
|
||||
if err := cd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
cs.cd = cd
|
||||
|
||||
if err := cs.getClient(cs.grpcSocket); err != nil {
|
||||
// Kill the daemon
|
||||
cs.cd.Process.Kill()
|
||||
return err
|
||||
}
|
||||
|
||||
// Monitor events
|
||||
events, err := cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: cs.lastEventTs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go cs.ContainerdEventsHandler(events)
|
||||
|
||||
go func() {
|
||||
cs.syncChild <- cd.Wait()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SetUpSuite(c *check.C) {
|
||||
bundleMap = make(map[string]Bundle)
|
||||
cs.eventFilters = make(map[string]func(event *types.Event))
|
||||
|
||||
// Get working directory for tests
|
||||
wd := utils.GetTestOutDir()
|
||||
if err := os.Chdir(wd); err != nil {
|
||||
c.Fatalf("Could not change working directory: %v", err)
|
||||
}
|
||||
cs.cwd = wd
|
||||
|
||||
// Clean old bundles
|
||||
os.RemoveAll(utils.BundlesRoot)
|
||||
|
||||
// Ensure the oci bundles directory exists
|
||||
if err := os.MkdirAll(utils.BundlesRoot, 0755); err != nil {
|
||||
c.Fatalf("Failed to create bundles directory: %v", err)
|
||||
}
|
||||
|
||||
// Generate the reference spec
|
||||
if err := utils.GenerateReferenceSpecs(utils.BundlesRoot); err != nil {
|
||||
c.Fatalf("Unable to generate OCI reference spec: %v", err)
|
||||
}
|
||||
|
||||
// Create our output directory
|
||||
cs.outputDir = fmt.Sprintf(utils.OutputDirFormat, time.Now().Format("2006-01-02_150405.000000"))
|
||||
|
||||
cs.stateDir = filepath.Join(cs.outputDir, "containerd-master")
|
||||
if err := os.MkdirAll(cs.stateDir, 0755); err != nil {
|
||||
c.Fatalf("Unable to created output directory '%s': %v", cs.stateDir, err)
|
||||
}
|
||||
|
||||
cs.grpcSocket = "unix://" + filepath.Join(cs.outputDir, "containerd-master", "containerd.sock")
|
||||
cdLogFile := filepath.Join(cs.outputDir, "containerd-master", "containerd.log")
|
||||
|
||||
f, err := os.OpenFile(cdLogFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR|os.O_SYNC, 0777)
|
||||
if err != nil {
|
||||
c.Fatalf("Failed to create master containerd log file: %v", err)
|
||||
}
|
||||
cs.logFile = f
|
||||
|
||||
cs.syncChild = make(chan error)
|
||||
cs.RestartDaemon(false)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TearDownSuite(c *check.C) {
|
||||
|
||||
// tell containerd to stop
|
||||
if cs.cd != nil {
|
||||
cs.cd.Process.Signal(os.Interrupt)
|
||||
|
||||
done := false
|
||||
for done == false {
|
||||
select {
|
||||
case err := <-cs.syncChild:
|
||||
if err != nil {
|
||||
c.Errorf("master containerd did not exit cleanly: %v", err)
|
||||
}
|
||||
done = true
|
||||
case <-time.After(3 * time.Second):
|
||||
fmt.Println("Timeout while waiting for containerd to exit, killing it!")
|
||||
cs.cd.Process.Kill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cs.logFile != nil {
|
||||
cs.logFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SetContainerEventFilter(id string, filter func(event *types.Event)) {
|
||||
cs.eventFiltersMutex.Lock()
|
||||
cs.eventFilters[id] = filter
|
||||
cs.eventFiltersMutex.Unlock()
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TearDownTest(c *check.C) {
|
||||
ctrs, err := cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to retrieve running containers: %v", err)
|
||||
}
|
||||
|
||||
// Kill all containers that survived
|
||||
for _, ctr := range ctrs {
|
||||
ch := make(chan interface{})
|
||||
cs.SetContainerEventFilter(ctr.Id, func(e *types.Event) {
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
ch <- nil
|
||||
}
|
||||
})
|
||||
|
||||
if err := cs.KillContainer(ctr.Id); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to cleanup leftover test containers: %v\n", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(3 * time.Second):
|
||||
fmt.Fprintf(os.Stderr, "TearDownTest: Containerd %v didn't die after 3 seconds\n", ctr.Id)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,321 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func (cs *ContainerdSuite) GetLogs() string {
|
||||
b, _ := ioutil.ReadFile(cs.logFile.Name())
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) Events(from time.Time, storedOnly bool, id string) (types.API_EventsClient, error) {
|
||||
var (
|
||||
ftsp *timestamp.Timestamp
|
||||
err error
|
||||
)
|
||||
if !from.IsZero() {
|
||||
ftsp, err = ptypes.TimestampProto(from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: ftsp, StoredOnly: storedOnly, Id: id})
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) ListRunningContainers() ([]*types.Container, error) {
|
||||
resp, err := cs.grpcClient.State(context.Background(), &types.StateRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Containers, nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SignalContainerProcess(id string, procID string, sig uint32) error {
|
||||
_, err := cs.grpcClient.Signal(context.Background(), &types.SignalRequest{
|
||||
Id: id,
|
||||
Pid: procID,
|
||||
Signal: sig,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) SignalContainer(id string, sig uint32) error {
|
||||
return cs.SignalContainerProcess(id, "init", sig)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) KillContainer(id string) error {
|
||||
return cs.SignalContainerProcess(id, "init", uint32(syscall.SIGKILL))
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) UpdateContainerResource(id string, rs *types.UpdateResource) error {
|
||||
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "",
|
||||
Resources: rs,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) PauseContainer(id string) error {
|
||||
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "paused",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) ResumeContainer(id string) error {
|
||||
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||
Id: id,
|
||||
Pid: "init",
|
||||
Status: "running",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) GetContainerStats(id string) (*types.StatsResponse, error) {
|
||||
stats, err := cs.grpcClient.Stats(context.Background(), &types.StatsRequest{
|
||||
Id: id,
|
||||
})
|
||||
return stats, err
|
||||
}
|
||||
|
||||
type stdio struct {
|
||||
stdin string
|
||||
stdout string
|
||||
stderr string
|
||||
stdinf *os.File
|
||||
stdoutf *os.File
|
||||
stderrf *os.File
|
||||
stdoutBuffer bytes.Buffer
|
||||
stderrBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
type ContainerProcess struct {
|
||||
containerID string
|
||||
pid string
|
||||
bundle *Bundle
|
||||
io stdio
|
||||
eventsCh chan *types.Event
|
||||
cs *ContainerdSuite
|
||||
hasExited bool
|
||||
}
|
||||
|
||||
func (c *ContainerProcess) openIo() (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
c.io.stdinf, err = os.OpenFile(c.io.stdin, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.io.stdoutf, err = os.OpenFile(c.io.stdout, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(&c.io.stdoutBuffer, c.io.stdoutf)
|
||||
|
||||
c.io.stderrf, err = os.OpenFile(c.io.stderr, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(&c.io.stderrBuffer, c.io.stderrf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContainerProcess) GetEventsChannel() chan *types.Event {
|
||||
return c.eventsCh
|
||||
}
|
||||
|
||||
func (c *ContainerProcess) GetNextEvent() *types.Event {
|
||||
if c.hasExited {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := <-c.eventsCh
|
||||
|
||||
if e.Type == "exit" && e.Pid == c.pid {
|
||||
c.Cleanup()
|
||||
c.hasExited = true
|
||||
close(c.eventsCh)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (c *ContainerProcess) CloseStdin() error {
|
||||
_, err := c.cs.grpcClient.UpdateProcess(context.Background(), &types.UpdateProcessRequest{
|
||||
Id: c.containerID,
|
||||
Pid: c.pid,
|
||||
CloseStdin: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ContainerProcess) Cleanup() {
|
||||
for _, f := range []*os.File{
|
||||
c.io.stdinf,
|
||||
c.io.stdoutf,
|
||||
c.io.stderrf,
|
||||
} {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
f = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewContainerProcess(cs *ContainerdSuite, bundle *Bundle, cid, pid string) (c *ContainerProcess, err error) {
|
||||
c = &ContainerProcess{
|
||||
containerID: cid,
|
||||
pid: "init",
|
||||
bundle: bundle,
|
||||
eventsCh: make(chan *types.Event, 8),
|
||||
cs: cs,
|
||||
hasExited: false,
|
||||
}
|
||||
|
||||
for name, path := range map[string]*string{
|
||||
"stdin": &c.io.stdin,
|
||||
"stdout": &c.io.stdout,
|
||||
"stderr": &c.io.stderr,
|
||||
} {
|
||||
*path = filepath.Join(bundle.Path, "io", cid+"-"+pid+"-"+name)
|
||||
if err = syscall.Mkfifo(*path, 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.openIo(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) StartContainerWithEventFilter(id, bundleName string, filter func(*types.Event)) (c *ContainerProcess, err error) {
|
||||
bundle := GetBundle(bundleName)
|
||||
if bundle == nil {
|
||||
return nil, fmt.Errorf("No such bundle '%s'", bundleName)
|
||||
}
|
||||
|
||||
c, err = NewContainerProcess(cs, bundle, id, "init")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &types.CreateContainerRequest{
|
||||
Id: id,
|
||||
BundlePath: filepath.Join(cs.cwd, bundle.Path),
|
||||
Stdin: filepath.Join(cs.cwd, c.io.stdin),
|
||||
Stdout: filepath.Join(cs.cwd, c.io.stdout),
|
||||
Stderr: filepath.Join(cs.cwd, c.io.stderr),
|
||||
}
|
||||
|
||||
if filter == nil {
|
||||
filter = func(event *types.Event) {
|
||||
c.eventsCh <- event
|
||||
}
|
||||
}
|
||||
|
||||
cs.SetContainerEventFilter(id, filter)
|
||||
|
||||
if _, err := cs.grpcClient.CreateContainer(context.Background(), r); err != nil {
|
||||
c.Cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) StartContainer(id, bundleName string) (c *ContainerProcess, err error) {
|
||||
return cs.StartContainerWithEventFilter(id, bundleName, nil)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) RunContainer(id, bundleName string) (c *ContainerProcess, err error) {
|
||||
c, err = cs.StartContainer(id, bundleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
e := c.GetNextEvent()
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) AddProcessToContainer(init *ContainerProcess, pid, cwd string, env, args []string, uid, gid uint32) (c *ContainerProcess, err error) {
|
||||
c, err = NewContainerProcess(cs, init.bundle, init.containerID, pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pr := &types.AddProcessRequest{
|
||||
Id: init.containerID,
|
||||
Pid: pid,
|
||||
Args: args,
|
||||
Cwd: cwd,
|
||||
Env: env,
|
||||
User: &types.User{
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
},
|
||||
Stdin: filepath.Join(cs.cwd, c.io.stdin),
|
||||
Stdout: filepath.Join(cs.cwd, c.io.stdout),
|
||||
Stderr: filepath.Join(cs.cwd, c.io.stderr),
|
||||
}
|
||||
|
||||
_, err = cs.grpcClient.AddProcess(context.Background(), pr)
|
||||
if err != nil {
|
||||
c.Cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type containerSorter struct {
|
||||
c []*types.Container
|
||||
}
|
||||
|
||||
func (s *containerSorter) Len() int {
|
||||
return len(s.c)
|
||||
}
|
||||
|
||||
func (s *containerSorter) Swap(i, j int) {
|
||||
s.c[i], s.c[j] = s.c[j], s.c[i]
|
||||
}
|
||||
|
||||
func (s *containerSorter) Less(i, j int) bool {
|
||||
return s.c[i].Id < s.c[j].Id
|
||||
}
|
||||
|
||||
func sortContainers(c []*types.Container) {
|
||||
sort.Sort(&containerSorter{c})
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func (cs *ContainerdSuite) TestEventsId(t *check.C) {
|
||||
if err := CreateBusyboxBundle("busybox-ls", []string{"ls"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
from := time.Now()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := cs.RunContainer(fmt.Sprintf("ls-%d", i), "busybox-ls")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
containerID := "ls-4"
|
||||
|
||||
events, err := cs.Events(from, true, containerID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
evs := []*types.Event{}
|
||||
for {
|
||||
e, err := events.Recv()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
break
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
evs = append(evs, e)
|
||||
}
|
||||
|
||||
t.Assert(len(evs), checker.Equals, 2)
|
||||
for idx, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "init",
|
||||
},
|
||||
} {
|
||||
evt.Timestamp = evs[idx].Timestamp
|
||||
t.Assert(*evs[idx], checker.Equals, evt)
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func (cs *ContainerdSuite) TestBusyboxTopExecEcho(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
initp *ContainerProcess
|
||||
echop *ContainerProcess
|
||||
)
|
||||
|
||||
containerID := "top"
|
||||
initp, err = cs.StartContainer(containerID, bundleName)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
echop, err = cs.AddProcessToContainer(initp, "echo", "/", []string{"PATH=/bin"}, []string{"sh", "-c", "echo -n Ay Caramba! ; exit 1"}, 0, 0)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "start-process",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "echo",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 1,
|
||||
Pid: "echo",
|
||||
},
|
||||
} {
|
||||
ch := initp.GetEventsChannel()
|
||||
e := <-ch
|
||||
evt.Timestamp = e.Timestamp
|
||||
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
}
|
||||
|
||||
t.Assert(echop.io.stdoutBuffer.String(), checker.Equals, "Ay Caramba!")
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestBusyboxTopExecTop(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
initp *ContainerProcess
|
||||
)
|
||||
|
||||
containerID := "top"
|
||||
initp, err = cs.StartContainer(containerID, bundleName)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
execID := "top1"
|
||||
_, err = cs.AddProcessToContainer(initp, execID, "/", []string{"PATH=/usr/bin"}, []string{"top"}, 0, 0)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
for idx, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "start-process",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: execID,
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 137,
|
||||
Pid: execID,
|
||||
},
|
||||
} {
|
||||
ch := initp.GetEventsChannel()
|
||||
e := <-ch
|
||||
evt.Timestamp = e.Timestamp
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
if idx == 1 {
|
||||
// Process Started, kill it
|
||||
cs.SignalContainerProcess(containerID, "top1", uint32(syscall.SIGKILL))
|
||||
}
|
||||
}
|
||||
|
||||
// Container should still be running
|
||||
containers, err := cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Assert(len(containers), checker.Equals, 1)
|
||||
t.Assert(containers[0].Id, checker.Equals, "top")
|
||||
t.Assert(containers[0].Status, checker.Equals, "running")
|
||||
t.Assert(containers[0].BundlePath, check.Equals, filepath.Join(cs.cwd, GetBundle(bundleName).Path))
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestBusyboxTopExecTopKillInit(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
initp *ContainerProcess
|
||||
)
|
||||
|
||||
containerID := "top"
|
||||
initp, err = cs.StartContainer(containerID, bundleName)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
execID := "top1"
|
||||
_, err = cs.AddProcessToContainer(initp, execID, "/", []string{"PATH=/usr/bin"}, []string{"top"}, 0, 0)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
ch := initp.GetEventsChannel()
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "start-process",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: execID,
|
||||
},
|
||||
} {
|
||||
e := <-ch
|
||||
evt.Timestamp = e.Timestamp
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
}
|
||||
|
||||
cs.SignalContainerProcess(containerID, "init", uint32(syscall.SIGTERM))
|
||||
for i := 0; i < 2; i++ {
|
||||
e := <-ch
|
||||
switch e.Pid {
|
||||
case "init":
|
||||
evt := types.Event{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 143,
|
||||
Pid: "init",
|
||||
Timestamp: e.Timestamp,
|
||||
}
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case execID:
|
||||
evt := types.Event{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 137,
|
||||
Pid: execID,
|
||||
Timestamp: e.Timestamp,
|
||||
}
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
default:
|
||||
t.Fatalf("Unexpected event %v", e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,538 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/containerd/api/grpc/types"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
ocs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxLsSlash(t *check.C) {
|
||||
expectedOutput := `bin
|
||||
dev
|
||||
etc
|
||||
home
|
||||
lib
|
||||
lib64
|
||||
linuxrc
|
||||
media
|
||||
mnt
|
||||
opt
|
||||
proc
|
||||
root
|
||||
run
|
||||
sbin
|
||||
sys
|
||||
tmp
|
||||
usr
|
||||
var
|
||||
`
|
||||
if err := CreateBusyboxBundle("busybox-ls-slash", []string{"ls", "/"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c, err := cs.RunContainer("myls", "busybox-ls-slash")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(c.io.stdoutBuffer.String(), checker.Equals, expectedOutput)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxNoSuchFile(t *check.C) {
|
||||
expectedOutput := `oci runtime error: exec: "NoSuchFile": executable file not found in $PATH`
|
||||
|
||||
if err := CreateBusyboxBundle("busybox-no-such-file", []string{"NoSuchFile"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := cs.RunContainer("NoSuchFile", "busybox-no-such-file")
|
||||
t.Assert(grpc.ErrorDesc(err), checker.Contains, expectedOutput)
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxTop(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "start-busybox-top"
|
||||
_, err := cs.StartContainer(containerID, bundleName)
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
|
||||
containers, err := cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Assert(len(containers), checker.Equals, 1)
|
||||
t.Assert(containers[0].Id, checker.Equals, containerID)
|
||||
t.Assert(containers[0].Status, checker.Equals, "running")
|
||||
t.Assert(containers[0].BundlePath, check.Equals, filepath.Join(cs.cwd, GetBundle(bundleName).Path))
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxLsEvents(t *check.C) {
|
||||
if err := CreateBusyboxBundle("busybox-ls", []string{"ls"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "ls-events"
|
||||
c, err := cs.StartContainer(containerID, "busybox-ls")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "init",
|
||||
},
|
||||
} {
|
||||
ch := c.GetEventsChannel()
|
||||
select {
|
||||
case e := <-ch:
|
||||
evt.Timestamp = e.Timestamp
|
||||
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Container took more than 2 seconds to terminate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxSleep(t *check.C) {
|
||||
if err := CreateBusyboxBundle("busybox-sleep-5", []string{"sleep", "5"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
filter := func(e *types.Event) {
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
ch <- nil
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
_, err := cs.StartContainerWithEventFilter("sleep5", "busybox-sleep-5", filter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We add a generous 20% marge of error
|
||||
select {
|
||||
case <-ch:
|
||||
t.Assert(uint64(time.Now().Sub(start)), checker.LessOrEqualThan, uint64(6*time.Second))
|
||||
case <-time.After(6 * time.Second):
|
||||
t.Fatal("Container took more than 6 seconds to exit")
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxTopKill(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "top-kill"
|
||||
c, err := cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
err = cs.KillContainer(containerID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 128 + uint32(syscall.SIGKILL),
|
||||
Pid: "init",
|
||||
},
|
||||
} {
|
||||
ch := c.GetEventsChannel()
|
||||
select {
|
||||
case e := <-ch:
|
||||
evt.Timestamp = e.Timestamp
|
||||
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Container took more than 2 seconds to terminate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxTopSignalSigterm(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "top-sigterm"
|
||||
c, err := cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
err = cs.SignalContainer(containerID, uint32(syscall.SIGTERM))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 128 + uint32(syscall.SIGTERM),
|
||||
Pid: "init",
|
||||
},
|
||||
} {
|
||||
ch := c.GetEventsChannel()
|
||||
select {
|
||||
case e := <-ch:
|
||||
evt.Timestamp = e.Timestamp
|
||||
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Container took more than 2 seconds to terminate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxTrapUSR1(t *check.C) {
|
||||
if err := CreateBusyboxBundle("busybox-trap-usr1", []string{"sh", "-c", "trap 'echo -n booh!' SIGUSR1 ; sleep 60 & wait"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "trap-usr1"
|
||||
c, err := cs.StartContainer(containerID, "busybox-trap-usr1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
if err := cs.SignalContainer(containerID, uint32(syscall.SIGUSR1)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
e := c.GetNextEvent()
|
||||
if e.Type == "exit" && e.Pid == "init" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t.Assert(c.io.stdoutBuffer.String(), checker.Equals, "booh!")
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestStartBusyboxTopPauseResume(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "top-pause-resume"
|
||||
c, err := cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cs.PauseContainer(containerID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cs.ResumeContainer(containerID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "pause",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "resume",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
} {
|
||||
ch := c.GetEventsChannel()
|
||||
select {
|
||||
case e := <-ch:
|
||||
evt.Timestamp = e.Timestamp
|
||||
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Container took more than 2 seconds to terminate")
|
||||
}
|
||||
}
|
||||
|
||||
// check that status is running
|
||||
containers, err := cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Assert(len(containers), checker.Equals, 1)
|
||||
t.Assert(containers[0].Id, checker.Equals, containerID)
|
||||
t.Assert(containers[0].Status, checker.Equals, "running")
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestOOM(t *check.C) {
|
||||
bundleName := "busybox-sh-512k-memlimit"
|
||||
if err := CreateBundleWithFilter("busybox", bundleName, []string{"sh", "-c", "x=oom-party-time; while true; do x=$x$x$x$x$x$x$x$x$x$x; done"}, func(spec *ocs.Spec) {
|
||||
// Limit to 512k for quick oom
|
||||
var limit uint64 = 8 * 1024 * 1024
|
||||
spec.Linux.Resources.Memory = &ocs.Memory{
|
||||
Limit: &limit,
|
||||
}
|
||||
if swapEnabled() {
|
||||
spec.Linux.Resources.Memory.Swap = &limit
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerID := "sh-oom"
|
||||
c, err := cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "oom",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 137,
|
||||
Pid: "init",
|
||||
},
|
||||
} {
|
||||
ch := c.GetEventsChannel()
|
||||
select {
|
||||
case e := <-ch:
|
||||
evt.Timestamp = e.Timestamp
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case <-time.After(60 * time.Second):
|
||||
t.Fatalf("Container took more than 60 seconds to %s", evt.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestRestart(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
totalCtr := 10
|
||||
|
||||
for i := 0; i < totalCtr; i++ {
|
||||
containerID := fmt.Sprintf("top%d", i)
|
||||
c, err := cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
e := c.GetNextEvent()
|
||||
|
||||
t.Assert(*e, checker.Equals, types.Event{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
Timestamp: e.Timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
// restart daemon gracefully (SIGINT)
|
||||
cs.RestartDaemon(false)
|
||||
|
||||
// check that status is running
|
||||
containers, err := cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sortContainers(containers)
|
||||
t.Assert(len(containers), checker.Equals, totalCtr)
|
||||
for i := 0; i < totalCtr; i++ {
|
||||
t.Assert(containers[i].Id, checker.Equals, fmt.Sprintf("top%d", i))
|
||||
t.Assert(containers[i].Status, checker.Equals, "running")
|
||||
}
|
||||
|
||||
// Now kill daemon (SIGKILL)
|
||||
cs.StopDaemon(true)
|
||||
|
||||
// Sleep a second to allow thevent e timestamp to change since
|
||||
// it's second based
|
||||
<-time.After(3 * time.Second)
|
||||
|
||||
// Kill a couple of containers
|
||||
killedCtr := map[int]bool{4: true, 2: true}
|
||||
|
||||
var f func(*types.Event)
|
||||
deathChans := make([]chan error, len(killedCtr))
|
||||
deathChansIdx := 0
|
||||
|
||||
for i := range killedCtr {
|
||||
ch := make(chan error, 1)
|
||||
deathChans[deathChansIdx] = ch
|
||||
deathChansIdx++
|
||||
syscall.Kill(int(containers[i].Pids[0]), syscall.SIGKILL)
|
||||
|
||||
// Filter to be notified of their death
|
||||
containerID := fmt.Sprintf("top%d", i)
|
||||
f = func(event *types.Event) {
|
||||
expectedEvent := types.Event{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 137,
|
||||
Pid: "init",
|
||||
}
|
||||
expectedEvent.Timestamp = event.Timestamp
|
||||
if ok := t.Check(*event, checker.Equals, expectedEvent); !ok {
|
||||
ch <- fmt.Errorf("Unexpected event: %#v", *event)
|
||||
} else {
|
||||
ch <- nil
|
||||
}
|
||||
}
|
||||
cs.SetContainerEventFilter(containerID, f)
|
||||
}
|
||||
|
||||
cs.RestartDaemon(true)
|
||||
|
||||
// Ensure we got our events
|
||||
for i := range deathChans {
|
||||
done := false
|
||||
for done == false {
|
||||
select {
|
||||
case err := <-deathChans[i]:
|
||||
t.Assert(err, checker.Equals, nil)
|
||||
done = true
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("Exit event for container not received after 3 seconds")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check that status is running
|
||||
containers, err = cs.ListRunningContainers()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sortContainers(containers)
|
||||
t.Assert(len(containers), checker.Equals, totalCtr-len(killedCtr))
|
||||
idShift := 0
|
||||
for i := 0; i < totalCtr-len(killedCtr); i++ {
|
||||
if _, ok := killedCtr[i+idShift]; ok {
|
||||
idShift++
|
||||
}
|
||||
t.Assert(containers[i].Id, checker.Equals, fmt.Sprintf("top%d", i+idShift))
|
||||
t.Assert(containers[i].Status, checker.Equals, "running")
|
||||
}
|
||||
}
|
||||
|
||||
func swapEnabled() bool {
|
||||
_, err := os.Stat("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (cs *ContainerdSuite) TestSigkillShimReuseName(t *check.C) {
|
||||
bundleName := "busybox-top"
|
||||
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
containerID := "top"
|
||||
c, err := cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Sigkill the shim
|
||||
exec.Command("pkill", "-9", "containerd-shim").Run()
|
||||
|
||||
// Wait for it to be reaped
|
||||
for _, evt := range []types.Event{
|
||||
{
|
||||
Type: "start-container",
|
||||
Id: containerID,
|
||||
Status: 0,
|
||||
Pid: "",
|
||||
},
|
||||
{
|
||||
Type: "exit",
|
||||
Id: containerID,
|
||||
Status: 128 + 9,
|
||||
Pid: "init",
|
||||
},
|
||||
} {
|
||||
ch := c.GetEventsChannel()
|
||||
select {
|
||||
case e := <-ch:
|
||||
evt.Timestamp = e.Timestamp
|
||||
|
||||
t.Assert(*e, checker.Equals, evt)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Container took more than 2 seconds to terminate")
|
||||
}
|
||||
}
|
||||
|
||||
// Start a new continer with the same name
|
||||
c, err = cs.StartContainer(containerID, bundleName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -1,738 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/specs"
|
||||
ocs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Container defines the operations allowed on a container
|
||||
type Container interface {
|
||||
// ID returns the container ID
|
||||
ID() string
|
||||
// Path returns the path to the bundle
|
||||
Path() string
|
||||
// Start starts the init process of the container
|
||||
Start(checkpointPath string, s Stdio) (Process, error)
|
||||
// Exec starts another process in an existing container
|
||||
Exec(string, specs.ProcessSpec, Stdio) (Process, error)
|
||||
// Delete removes the container's state and any resources
|
||||
Delete() error
|
||||
// Processes returns all the containers processes that have been added
|
||||
Processes() ([]Process, error)
|
||||
// State returns the containers runtime state
|
||||
State() State
|
||||
// Resume resumes a paused container
|
||||
Resume() error
|
||||
// Pause pauses a running container
|
||||
Pause() error
|
||||
// RemoveProcess removes the specified process from the container
|
||||
RemoveProcess(string) error
|
||||
// Checkpoints returns all the checkpoints for a container
|
||||
Checkpoints(checkpointDir string) ([]Checkpoint, error)
|
||||
// Checkpoint creates a new checkpoint
|
||||
Checkpoint(checkpoint Checkpoint, checkpointDir string) error
|
||||
// DeleteCheckpoint deletes the checkpoint for the provided name
|
||||
DeleteCheckpoint(name string, checkpointDir string) error
|
||||
// Labels are user provided labels for the container
|
||||
Labels() []string
|
||||
// Pids returns all pids inside the container
|
||||
Pids() ([]int, error)
|
||||
// Stats returns realtime container stats and resource information
|
||||
Stats() (*Stat, error)
|
||||
// Name or path of the OCI compliant runtime used to execute the container
|
||||
Runtime() string
|
||||
// OOM signals the channel if the container received an OOM notification
|
||||
OOM() (OOM, error)
|
||||
// UpdateResource updates the containers resources to new values
|
||||
UpdateResources(*Resource) error
|
||||
|
||||
// Status return the current status of the container.
|
||||
Status() (State, error)
|
||||
}
|
||||
|
||||
// OOM wraps a container OOM.
|
||||
type OOM interface {
|
||||
io.Closer
|
||||
FD() int
|
||||
ContainerID() string
|
||||
Flush() error
|
||||
Removed() bool
|
||||
}
|
||||
|
||||
// Stdio holds the path to the 3 pipes used for the standard ios.
|
||||
type Stdio struct {
|
||||
Stdin string
|
||||
Stdout string
|
||||
Stderr string
|
||||
}
|
||||
|
||||
// NewStdio wraps the given standard io path into an Stdio struct.
|
||||
// If a given parameter is the empty string, it is replaced by "/dev/null"
|
||||
func NewStdio(stdin, stdout, stderr string) Stdio {
|
||||
for _, s := range []*string{
|
||||
&stdin, &stdout, &stderr,
|
||||
} {
|
||||
if *s == "" {
|
||||
*s = "/dev/null"
|
||||
}
|
||||
}
|
||||
return Stdio{
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerOpts keeps the options passed at container creation
|
||||
type ContainerOpts struct {
|
||||
Root string
|
||||
ID string
|
||||
Bundle string
|
||||
Runtime string
|
||||
RuntimeArgs []string
|
||||
Shim string
|
||||
Labels []string
|
||||
NoPivotRoot bool
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// New returns a new container
|
||||
func New(opts ContainerOpts) (Container, error) {
|
||||
c := &container{
|
||||
root: opts.Root,
|
||||
id: opts.ID,
|
||||
bundle: opts.Bundle,
|
||||
labels: opts.Labels,
|
||||
processes: make(map[string]*process),
|
||||
runtime: opts.Runtime,
|
||||
runtimeArgs: opts.RuntimeArgs,
|
||||
shim: opts.Shim,
|
||||
noPivotRoot: opts.NoPivotRoot,
|
||||
timeout: opts.Timeout,
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(c.root, c.id), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Create(filepath.Join(c.root, c.id, StateFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewEncoder(f).Encode(state{
|
||||
Bundle: c.bundle,
|
||||
Labels: c.labels,
|
||||
Runtime: c.runtime,
|
||||
RuntimeArgs: c.runtimeArgs,
|
||||
Shim: c.shim,
|
||||
NoPivotRoot: opts.NoPivotRoot,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Load return a new container from the matchin state file on disk.
|
||||
func Load(root, id, shimName string, timeout time.Duration) (Container, error) {
|
||||
var s state
|
||||
f, err := os.Open(filepath.Join(root, id, StateFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewDecoder(f).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &container{
|
||||
root: root,
|
||||
id: id,
|
||||
bundle: s.Bundle,
|
||||
labels: s.Labels,
|
||||
runtime: s.Runtime,
|
||||
runtimeArgs: s.RuntimeArgs,
|
||||
shim: s.Shim,
|
||||
noPivotRoot: s.NoPivotRoot,
|
||||
processes: make(map[string]*process),
|
||||
timeout: timeout,
|
||||
}
|
||||
|
||||
if c.shim == "" {
|
||||
c.shim = shimName
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(filepath.Join(root, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
pid := d.Name()
|
||||
s, err := readProcessState(filepath.Join(root, id, pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := loadProcess(filepath.Join(root, id, pid), pid, c, s)
|
||||
if err != nil {
|
||||
logrus.WithField("id", id).WithField("pid", pid).Debug("containerd: error loading process %s", err)
|
||||
continue
|
||||
}
|
||||
c.processes[pid] = p
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func readProcessState(dir string) (*ProcessState, error) {
|
||||
f, err := os.Open(filepath.Join(dir, "process.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
var s ProcessState
|
||||
if err := json.NewDecoder(f).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
type container struct {
|
||||
// path to store runtime state information
|
||||
root string
|
||||
id string
|
||||
bundle string
|
||||
runtime string
|
||||
runtimeArgs []string
|
||||
shim string
|
||||
processes map[string]*process
|
||||
labels []string
|
||||
oomFds []int
|
||||
noPivotRoot bool
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (c *container) ID() string {
|
||||
return c.id
|
||||
}
|
||||
|
||||
func (c *container) Path() string {
|
||||
return c.bundle
|
||||
}
|
||||
|
||||
func (c *container) Labels() []string {
|
||||
return c.labels
|
||||
}
|
||||
|
||||
func (c *container) readSpec() (*specs.Spec, error) {
|
||||
var spec specs.Spec
|
||||
f, err := os.Open(filepath.Join(c.bundle, "config.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
func (c *container) Delete() error {
|
||||
err := os.RemoveAll(filepath.Join(c.root, c.id))
|
||||
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "delete", c.id)
|
||||
if b, derr := exec.Command(c.runtime, args...).CombinedOutput(); err != nil {
|
||||
err = fmt.Errorf("%s: %q", derr, string(b))
|
||||
} else if len(b) > 0 {
|
||||
logrus.Debugf("%v %v: %q", c.runtime, args, string(b))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *container) Processes() ([]Process, error) {
|
||||
out := []Process{}
|
||||
for _, p := range c.processes {
|
||||
out = append(out, p)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *container) RemoveProcess(pid string) error {
|
||||
delete(c.processes, pid)
|
||||
return os.RemoveAll(filepath.Join(c.root, c.id, pid))
|
||||
}
|
||||
|
||||
func (c *container) State() State {
|
||||
proc := c.processes["init"]
|
||||
if proc == nil {
|
||||
return Stopped
|
||||
}
|
||||
return proc.State()
|
||||
}
|
||||
|
||||
func (c *container) Runtime() string {
|
||||
return c.runtime
|
||||
}
|
||||
|
||||
func (c *container) Pause() error {
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "pause", c.id)
|
||||
b, err := exec.Command(c.runtime, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %q", err.Error(), string(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *container) Resume() error {
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "resume", c.id)
|
||||
b, err := exec.Command(c.runtime, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %q", err.Error(), string(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *container) Checkpoints(checkpointDir string) ([]Checkpoint, error) {
|
||||
if checkpointDir == "" {
|
||||
checkpointDir = filepath.Join(c.bundle, "checkpoints")
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(checkpointDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []Checkpoint
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(checkpointDir, d.Name(), "config.json")
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cpt Checkpoint
|
||||
if err := json.Unmarshal(data, &cpt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, cpt)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *container) Checkpoint(cpt Checkpoint, checkpointDir string) error {
|
||||
if checkpointDir == "" {
|
||||
checkpointDir = filepath.Join(c.bundle, "checkpoints")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(checkpointDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := filepath.Join(checkpointDir, cpt.Name)
|
||||
if err := os.Mkdir(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Create(filepath.Join(path, "config.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cpt.Created = time.Now()
|
||||
err = json.NewEncoder(f).Encode(cpt)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := []string{
|
||||
"checkpoint",
|
||||
"--image-path", path,
|
||||
"--work-path", filepath.Join(path, "criu.work"),
|
||||
}
|
||||
add := func(flags ...string) {
|
||||
args = append(args, flags...)
|
||||
}
|
||||
add(c.runtimeArgs...)
|
||||
if !cpt.Exit {
|
||||
add("--leave-running")
|
||||
}
|
||||
if cpt.Shell {
|
||||
add("--shell-job")
|
||||
}
|
||||
if cpt.TCP {
|
||||
add("--tcp-established")
|
||||
}
|
||||
if cpt.UnixSockets {
|
||||
add("--ext-unix-sk")
|
||||
}
|
||||
for _, ns := range cpt.EmptyNS {
|
||||
add("--empty-ns", ns)
|
||||
}
|
||||
add(c.id)
|
||||
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %q", err.Error(), string(out))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *container) DeleteCheckpoint(name string, checkpointDir string) error {
|
||||
if checkpointDir == "" {
|
||||
checkpointDir = filepath.Join(c.bundle, "checkpoints")
|
||||
}
|
||||
return os.RemoveAll(filepath.Join(checkpointDir, name))
|
||||
}
|
||||
|
||||
func (c *container) Start(checkpointPath string, s Stdio) (Process, error) {
|
||||
processRoot := filepath.Join(c.root, c.id, InitProcessID)
|
||||
if err := os.Mkdir(processRoot, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.Command(c.shim,
|
||||
c.id, c.bundle, c.runtime,
|
||||
)
|
||||
cmd.Dir = processRoot
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
spec, err := c.readSpec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &processConfig{
|
||||
checkpoint: checkpointPath,
|
||||
root: processRoot,
|
||||
id: InitProcessID,
|
||||
c: c,
|
||||
stdio: s,
|
||||
spec: spec,
|
||||
processSpec: specs.ProcessSpec(spec.Process),
|
||||
}
|
||||
p, err := newProcess(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.createCmd(InitProcessID, cmd, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (c *container) Exec(pid string, pspec specs.ProcessSpec, s Stdio) (pp Process, err error) {
|
||||
processRoot := filepath.Join(c.root, c.id, pid)
|
||||
if err := os.Mkdir(processRoot, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.RemoveProcess(pid)
|
||||
}
|
||||
}()
|
||||
cmd := exec.Command(c.shim,
|
||||
c.id, c.bundle, c.runtime,
|
||||
)
|
||||
cmd.Dir = processRoot
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
spec, err := c.readSpec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &processConfig{
|
||||
exec: true,
|
||||
id: pid,
|
||||
root: processRoot,
|
||||
c: c,
|
||||
processSpec: pspec,
|
||||
spec: spec,
|
||||
stdio: s,
|
||||
}
|
||||
p, err := newProcess(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.createCmd(pid, cmd, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (c *container) createCmd(pid string, cmd *exec.Cmd, p *process) error {
|
||||
p.cmd = cmd
|
||||
if err := cmd.Start(); err != nil {
|
||||
close(p.cmdDoneCh)
|
||||
if exErr, ok := err.(*exec.Error); ok {
|
||||
if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist {
|
||||
return fmt.Errorf("%s not installed on system", c.shim)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
// We need the pid file to have been written to run
|
||||
defer func() {
|
||||
go func() {
|
||||
err := p.cmd.Wait()
|
||||
if err == nil {
|
||||
p.cmdSuccess = true
|
||||
}
|
||||
|
||||
if same, err := p.isSameProcess(); same && p.pid > 0 {
|
||||
// The process changed its PR_SET_PDEATHSIG, so force
|
||||
// kill it
|
||||
logrus.Infof("containerd: %s:%s (pid %v) has become an orphan, killing it", p.container.id, p.id, p.pid)
|
||||
err = unix.Kill(p.pid, syscall.SIGKILL)
|
||||
if err != nil && err != syscall.ESRCH {
|
||||
logrus.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err)
|
||||
} else {
|
||||
for {
|
||||
err = unix.Kill(p.pid, 0)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
close(p.cmdDoneCh)
|
||||
}()
|
||||
}()
|
||||
if err := c.waitForCreate(p, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
c.processes[pid] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
func hostIDFromMap(id uint32, mp []ocs.IDMapping) int {
|
||||
for _, m := range mp {
|
||||
if (id >= m.ContainerID) && (id <= (m.ContainerID + m.Size - 1)) {
|
||||
return int(m.HostID + (id - m.ContainerID))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *container) Pids() ([]int, error) {
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "ps", "--format=json", c.id)
|
||||
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %q", err.Error(), out)
|
||||
}
|
||||
var pids []int
|
||||
if err := json.Unmarshal(out, &pids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pids, nil
|
||||
}
|
||||
|
||||
func (c *container) Stats() (*Stat, error) {
|
||||
now := time.Now()
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "events", "--stats", c.id)
|
||||
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %q", err.Error(), out)
|
||||
}
|
||||
s := struct {
|
||||
Data *Stat `json:"data"`
|
||||
}{}
|
||||
if err := json.Unmarshal(out, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Data.Timestamp = now
|
||||
return s.Data, nil
|
||||
}
|
||||
|
||||
// Status implements the runtime Container interface.
|
||||
func (c *container) Status() (State, error) {
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "state", c.id)
|
||||
|
||||
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s: %q", err.Error(), out)
|
||||
}
|
||||
|
||||
// We only require the runtime json output to have a top level Status field.
|
||||
var s struct {
|
||||
Status State `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(out, &s); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.Status, nil
|
||||
}
|
||||
|
||||
func (c *container) writeEventFD(root string, cfd, efd int) error {
|
||||
f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd))
|
||||
return err
|
||||
}
|
||||
|
||||
type waitArgs struct {
|
||||
pid int
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error {
|
||||
wc := make(chan error, 1)
|
||||
go func() {
|
||||
for {
|
||||
if _, err := p.getPidFromFile(); err != nil {
|
||||
if os.IsNotExist(err) || err == errInvalidPidInt {
|
||||
alive, err := isAlive(cmd)
|
||||
if err != nil {
|
||||
wc <- err
|
||||
return
|
||||
}
|
||||
if !alive {
|
||||
// runc could have failed to run the container so lets get the error
|
||||
// out of the logs or the shim could have encountered an error
|
||||
messages, err := readLogMessages(filepath.Join(p.root, "shim-log.json"))
|
||||
if err != nil {
|
||||
wc <- err
|
||||
return
|
||||
}
|
||||
for _, m := range messages {
|
||||
if m.Level == "error" {
|
||||
wc <- fmt.Errorf("shim error: %v", m.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
// no errors reported back from shim, check for runc/runtime errors
|
||||
messages, err = readLogMessages(filepath.Join(p.root, "log.json"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = ErrContainerNotStarted
|
||||
}
|
||||
wc <- err
|
||||
return
|
||||
}
|
||||
for _, m := range messages {
|
||||
if m.Level == "error" {
|
||||
wc <- fmt.Errorf("oci runtime error: %v", m.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
wc <- ErrContainerNotStarted
|
||||
return
|
||||
}
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
wc <- err
|
||||
return
|
||||
}
|
||||
// the pid file was read successfully
|
||||
wc <- nil
|
||||
return
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case err := <-wc:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.saveStartTime()
|
||||
if err != nil {
|
||||
logrus.Warnf("containerd: unable to save %s:%s starttime: %v", p.container.id, p.id, err)
|
||||
}
|
||||
return nil
|
||||
case <-time.After(c.timeout):
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
return ErrContainerStartTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// isAlive checks if the shim that launched the container is still alive
|
||||
func isAlive(cmd *exec.Cmd) (bool, error) {
|
||||
if _, err := syscall.Wait4(cmd.Process.Pid, nil, syscall.WNOHANG, nil); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if err := syscall.Kill(cmd.Process.Pid, 0); err != nil {
|
||||
if err == syscall.ESRCH {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type oom struct {
|
||||
id string
|
||||
root string
|
||||
control *os.File
|
||||
eventfd int
|
||||
}
|
||||
|
||||
func (o *oom) ContainerID() string {
|
||||
return o.id
|
||||
}
|
||||
|
||||
func (o *oom) FD() int {
|
||||
return o.eventfd
|
||||
}
|
||||
|
||||
func (o *oom) Flush() error {
|
||||
buf := make([]byte, 8)
|
||||
_, err := syscall.Read(o.eventfd, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *oom) Removed() bool {
|
||||
_, err := os.Lstat(filepath.Join(o.root, "cgroup.event_control"))
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func (o *oom) Close() error {
|
||||
err := syscall.Close(o.eventfd)
|
||||
if cerr := o.control.Close(); err == nil {
|
||||
err = cerr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type message struct {
|
||||
Level string `json:"level"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func readLogMessages(path string) ([]message, error) {
|
||||
var out []message
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
dec := json.NewDecoder(f)
|
||||
for {
|
||||
var m message
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, nil
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/containerd/specs"
|
||||
ocs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
func findCgroupMountpointAndRoot(pid int, subsystem string) (string, string, error) {
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
txt := scanner.Text()
|
||||
fields := strings.Split(txt, " ")
|
||||
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
|
||||
if opt == subsystem {
|
||||
return fields[4], fields[3], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("cgroup path for %s not found", subsystem)
|
||||
}
|
||||
|
||||
func parseCgroupFile(path string) (map[string]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
cgroups := make(map[string]string)
|
||||
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text := s.Text()
|
||||
parts := strings.Split(text, ":")
|
||||
|
||||
for _, subs := range strings.Split(parts[1], ",") {
|
||||
cgroups[subs] = parts[2]
|
||||
}
|
||||
}
|
||||
return cgroups, nil
|
||||
}
|
||||
|
||||
func (c *container) OOM() (OOM, error) {
|
||||
p := c.processes[InitProcessID]
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("no init process found")
|
||||
}
|
||||
|
||||
mountpoint, hostRoot, err := findCgroupMountpointAndRoot(os.Getpid(), "memory")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cgroups, err := parseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", p.pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root, ok := cgroups["memory"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no memory cgroup for container %s", c.ID())
|
||||
}
|
||||
|
||||
// Take care of the case were we're running inside a container
|
||||
// ourself
|
||||
root = strings.TrimPrefix(root, hostRoot)
|
||||
|
||||
return c.getMemeoryEventFD(filepath.Join(mountpoint, root))
|
||||
}
|
||||
|
||||
func u64Ptr(i uint64) *uint64 { return &i }
|
||||
|
||||
func (c *container) UpdateResources(r *Resource) error {
|
||||
sr := ocs.Resources{
|
||||
Memory: &ocs.Memory{
|
||||
Limit: u64Ptr(uint64(r.Memory)),
|
||||
Reservation: u64Ptr(uint64(r.MemoryReservation)),
|
||||
Swap: u64Ptr(uint64(r.MemorySwap)),
|
||||
Kernel: u64Ptr(uint64(r.KernelMemory)),
|
||||
KernelTCP: u64Ptr(uint64(r.KernelTCPMemory)),
|
||||
},
|
||||
CPU: &ocs.CPU{
|
||||
Shares: u64Ptr(uint64(r.CPUShares)),
|
||||
Quota: u64Ptr(uint64(r.CPUQuota)),
|
||||
Period: u64Ptr(uint64(r.CPUPeriod)),
|
||||
Cpus: &r.CpusetCpus,
|
||||
Mems: &r.CpusetMems,
|
||||
},
|
||||
BlockIO: &ocs.BlockIO{
|
||||
Weight: &r.BlkioWeight,
|
||||
},
|
||||
}
|
||||
|
||||
srStr := bytes.NewBuffer(nil)
|
||||
if err := json.NewEncoder(srStr).Encode(&sr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := c.runtimeArgs
|
||||
args = append(args, "update", "-r", "-", c.id)
|
||||
cmd := exec.Command(c.runtime, args...)
|
||||
cmd.Stdin = srStr
|
||||
b, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf(string(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRootIDs(s *specs.Spec) (int, int, error) {
|
||||
if s == nil {
|
||||
return 0, 0, nil
|
||||
}
|
||||
var hasUserns bool
|
||||
for _, ns := range s.Linux.Namespaces {
|
||||
if ns.Type == ocs.UserNamespace {
|
||||
hasUserns = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasUserns {
|
||||
return 0, 0, nil
|
||||
}
|
||||
uid := hostIDFromMap(0, s.Linux.UIDMappings)
|
||||
gid := hostIDFromMap(0, s.Linux.GIDMappings)
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func (c *container) getMemeoryEventFD(root string) (*oom, error) {
|
||||
f, err := os.Open(filepath.Join(root, "memory.oom_control"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd, _, serr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
|
||||
if serr != 0 {
|
||||
f.Close()
|
||||
return nil, serr
|
||||
}
|
||||
if err := c.writeEventFD(root, int(f.Fd()), int(fd)); err != nil {
|
||||
syscall.Close(int(fd))
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &oom{
|
||||
root: root,
|
||||
id: c.id,
|
||||
eventfd: int(fd),
|
||||
control: f,
|
||||
}, nil
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/docker/containerd/specs"
|
||||
)
|
||||
|
||||
func (c *container) OOM() (OOM, error) {
|
||||
return nil, errors.New("runtime OOM() not implemented on Solaris")
|
||||
}
|
||||
|
||||
func (c *container) UpdateResources(r *Resource) error {
|
||||
return errors.New("runtime UpdateResources() not implemented on Solaris")
|
||||
}
|
||||
|
||||
func getRootIDs(s *specs.Spec) (int, int, error) {
|
||||
return 0, 0, errors.New("runtime getRootIDs() not implemented on Solaris")
|
||||
}
|
|
@ -1,467 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/specs"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Process holds the operation allowed on a container's process
|
||||
type Process interface {
|
||||
io.Closer
|
||||
|
||||
// ID of the process.
|
||||
// This is either "init" when it is the container's init process or
|
||||
// it is a user provided id for the process similar to the container id
|
||||
ID() string
|
||||
// Start unblocks the associated container init process.
|
||||
// This should only be called on the process with ID "init"
|
||||
Start() error
|
||||
CloseStdin() error
|
||||
Resize(int, int) error
|
||||
// FD returns the fd the provides an event when the process exits
|
||||
FD() int
|
||||
// ExitStatus returns the exit status of the process or an error if it
|
||||
// has not exited
|
||||
ExitStatus() (uint32, error)
|
||||
// Spec returns the process spec that created the process
|
||||
Spec() specs.ProcessSpec
|
||||
// Signal sends the provided signal to the process
|
||||
Signal(os.Signal) error
|
||||
// Container returns the container that the process belongs to
|
||||
Container() Container
|
||||
// Stdio of the container
|
||||
Stdio() Stdio
|
||||
// SystemPid is the pid on the system
|
||||
SystemPid() int
|
||||
// State returns if the process is running or not
|
||||
State() State
|
||||
// Wait reaps the shim process if avaliable
|
||||
Wait()
|
||||
}
|
||||
|
||||
type processConfig struct {
|
||||
id string
|
||||
root string
|
||||
processSpec specs.ProcessSpec
|
||||
spec *specs.Spec
|
||||
c *container
|
||||
stdio Stdio
|
||||
exec bool
|
||||
checkpoint string
|
||||
}
|
||||
|
||||
func newProcess(config *processConfig) (*process, error) {
|
||||
p := &process{
|
||||
root: config.root,
|
||||
id: config.id,
|
||||
container: config.c,
|
||||
spec: config.processSpec,
|
||||
stdio: config.stdio,
|
||||
cmdDoneCh: make(chan struct{}),
|
||||
state: Running,
|
||||
}
|
||||
uid, gid, err := getRootIDs(config.spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Create(filepath.Join(config.root, "process.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
ps := ProcessState{
|
||||
ProcessSpec: config.processSpec,
|
||||
Exec: config.exec,
|
||||
PlatformProcessState: PlatformProcessState{
|
||||
Checkpoint: config.checkpoint,
|
||||
RootUID: uid,
|
||||
RootGID: gid,
|
||||
},
|
||||
Stdin: config.stdio.Stdin,
|
||||
Stdout: config.stdio.Stdout,
|
||||
Stderr: config.stdio.Stderr,
|
||||
RuntimeArgs: config.c.runtimeArgs,
|
||||
NoPivotRoot: config.c.noPivotRoot,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(f).Encode(ps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
control, err := getControlPipe(filepath.Join(config.root, ControlFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.exitPipe = exit
|
||||
p.controlPipe = control
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) {
|
||||
p := &process{
|
||||
root: root,
|
||||
id: id,
|
||||
container: c,
|
||||
spec: s.ProcessSpec,
|
||||
stdio: Stdio{
|
||||
Stdin: s.Stdin,
|
||||
Stdout: s.Stdout,
|
||||
Stderr: s.Stderr,
|
||||
},
|
||||
state: Stopped,
|
||||
}
|
||||
|
||||
startTime, err := ioutil.ReadFile(filepath.Join(p.root, StartTimeFile))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
p.startTime = string(startTime)
|
||||
|
||||
if _, err := p.getPidFromFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := p.ExitStatus(); err != nil {
|
||||
if err == ErrProcessNotExited {
|
||||
exit, err := getExitPipe(filepath.Join(root, ExitFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.exitPipe = exit
|
||||
|
||||
control, err := getControlPipe(filepath.Join(root, ControlFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.controlPipe = control
|
||||
|
||||
p.state = Running
|
||||
return p, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func readProcStatField(pid int, field int) (string, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join(string(filepath.Separator), "proc", strconv.Itoa(pid), "stat"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if field > 2 {
|
||||
// First, split out the name since he could contains spaces.
|
||||
parts := strings.Split(string(data), ") ")
|
||||
// Now split out the rest, we end up with 2 fields less
|
||||
parts = strings.Split(parts[1], " ")
|
||||
return parts[field-2-1], nil // field count start at 1 in manual
|
||||
}
|
||||
|
||||
parts := strings.Split(string(data), " (")
|
||||
|
||||
if field == 1 {
|
||||
return parts[0], nil
|
||||
}
|
||||
|
||||
parts = strings.Split(parts[1], ") ")
|
||||
return parts[0], nil
|
||||
}
|
||||
|
||||
type process struct {
|
||||
root string
|
||||
id string
|
||||
pid int
|
||||
exitPipe *os.File
|
||||
controlPipe *os.File
|
||||
container *container
|
||||
spec specs.ProcessSpec
|
||||
stdio Stdio
|
||||
cmd *exec.Cmd
|
||||
cmdSuccess bool
|
||||
cmdDoneCh chan struct{}
|
||||
state State
|
||||
stateLock sync.Mutex
|
||||
startTime string
|
||||
}
|
||||
|
||||
func (p *process) ID() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
func (p *process) Container() Container {
|
||||
return p.container
|
||||
}
|
||||
|
||||
func (p *process) SystemPid() int {
|
||||
return p.pid
|
||||
}
|
||||
|
||||
// FD returns the fd of the exit pipe
|
||||
func (p *process) FD() int {
|
||||
return int(p.exitPipe.Fd())
|
||||
}
|
||||
|
||||
func (p *process) CloseStdin() error {
|
||||
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *process) Resize(w, h int) error {
|
||||
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *process) updateExitStatusFile(status uint32) (uint32, error) {
|
||||
p.stateLock.Lock()
|
||||
p.state = Stopped
|
||||
p.stateLock.Unlock()
|
||||
err := ioutil.WriteFile(filepath.Join(p.root, ExitStatusFile), []byte(fmt.Sprintf("%u", status)), 0644)
|
||||
return status, err
|
||||
}
|
||||
|
||||
func (p *process) handleSigkilledShim(rst uint32, rerr error) (uint32, error) {
|
||||
if p.cmd == nil || p.cmd.Process == nil {
|
||||
e := unix.Kill(p.pid, 0)
|
||||
if e == syscall.ESRCH {
|
||||
logrus.Warnf("containerd: %s:%s (pid %d) does not exist", p.container.id, p.id, p.pid)
|
||||
// The process died while containerd was down (probably of
|
||||
// SIGKILL, but no way to be sure)
|
||||
return p.updateExitStatusFile(UnknownStatus)
|
||||
}
|
||||
|
||||
// If it's not the same process, just mark it stopped and set
|
||||
// the status to the UnknownStatus value (i.e. 255)
|
||||
if same, err := p.isSameProcess(); !same {
|
||||
logrus.Warnf("containerd: %s:%s (pid %d) is not the same process anymore (%v)", p.container.id, p.id, p.pid, err)
|
||||
// Create the file so we get the exit event generated once monitor kicks in
|
||||
// without having to go through all this process again
|
||||
return p.updateExitStatusFile(UnknownStatus)
|
||||
}
|
||||
|
||||
ppid, err := readProcStatField(p.pid, 4)
|
||||
if err != nil {
|
||||
return rst, fmt.Errorf("could not check process ppid: %v (%v)", err, rerr)
|
||||
}
|
||||
if ppid == "1" {
|
||||
logrus.Warnf("containerd: %s:%s shim died, killing associated process", p.container.id, p.id)
|
||||
unix.Kill(p.pid, syscall.SIGKILL)
|
||||
if err != nil && err != syscall.ESRCH {
|
||||
return UnknownStatus, fmt.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err)
|
||||
}
|
||||
|
||||
// wait for the process to die
|
||||
for {
|
||||
e := unix.Kill(p.pid, 0)
|
||||
if e == syscall.ESRCH {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
// Create the file so we get the exit event generated once monitor kicks in
|
||||
// without having to go through all this process again
|
||||
return p.updateExitStatusFile(128 + uint32(syscall.SIGKILL))
|
||||
}
|
||||
|
||||
return rst, rerr
|
||||
}
|
||||
|
||||
// Possible that the shim was SIGKILLED
|
||||
e := unix.Kill(p.cmd.Process.Pid, 0)
|
||||
if e != syscall.ESRCH {
|
||||
return rst, rerr
|
||||
}
|
||||
|
||||
// Ensure we got the shim ProcessState
|
||||
<-p.cmdDoneCh
|
||||
|
||||
shimStatus := p.cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||
if shimStatus.Signaled() && shimStatus.Signal() == syscall.SIGKILL {
|
||||
logrus.Debugf("containerd: ExitStatus(container: %s, process: %s): shim was SIGKILL'ed reaping its child with pid %d", p.container.id, p.id, p.pid)
|
||||
|
||||
rerr = nil
|
||||
rst = 128 + uint32(shimStatus.Signal())
|
||||
|
||||
p.stateLock.Lock()
|
||||
p.state = Stopped
|
||||
p.stateLock.Unlock()
|
||||
}
|
||||
|
||||
return rst, rerr
|
||||
}
|
||||
|
||||
func (p *process) ExitStatus() (rst uint32, rerr error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
rst, rerr = p.handleSigkilledShim(rst, rerr)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return UnknownStatus, ErrProcessNotExited
|
||||
}
|
||||
return UnknownStatus, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return UnknownStatus, ErrProcessNotExited
|
||||
}
|
||||
p.stateLock.Lock()
|
||||
p.state = Stopped
|
||||
p.stateLock.Unlock()
|
||||
|
||||
i, err := strconv.ParseUint(string(data), 10, 32)
|
||||
return uint32(i), err
|
||||
}
|
||||
|
||||
func (p *process) Spec() specs.ProcessSpec {
|
||||
return p.spec
|
||||
}
|
||||
|
||||
func (p *process) Stdio() Stdio {
|
||||
return p.stdio
|
||||
}
|
||||
|
||||
// Close closes any open files and/or resouces on the process
|
||||
func (p *process) Close() error {
|
||||
err := p.exitPipe.Close()
|
||||
if cerr := p.controlPipe.Close(); err == nil {
|
||||
err = cerr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *process) State() State {
|
||||
p.stateLock.Lock()
|
||||
defer p.stateLock.Unlock()
|
||||
return p.state
|
||||
}
|
||||
|
||||
func (p *process) getPidFromFile() (int, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
i, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
return -1, errInvalidPidInt
|
||||
}
|
||||
p.pid = i
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (p *process) readStartTime() (string, error) {
|
||||
return readProcStatField(p.pid, 22)
|
||||
}
|
||||
|
||||
func (p *process) saveStartTime() error {
|
||||
startTime, err := p.readStartTime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.startTime = startTime
|
||||
return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644)
|
||||
}
|
||||
|
||||
func (p *process) isSameProcess() (bool, error) {
|
||||
// for backward compat assume it's the same if startTime wasn't set
|
||||
if p.startTime == "" {
|
||||
return true, nil
|
||||
}
|
||||
if p.pid == 0 {
|
||||
_, err := p.getPidFromFile()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
startTime, err := p.readStartTime()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return startTime == p.startTime, nil
|
||||
}
|
||||
|
||||
// Wait will reap the shim process
|
||||
func (p *process) Wait() {
|
||||
if p.cmdDoneCh != nil {
|
||||
<-p.cmdDoneCh
|
||||
}
|
||||
}
|
||||
|
||||
func getExitPipe(path string) (*os.File, error) {
|
||||
if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
// add NONBLOCK in case the other side has already closed or else
|
||||
// this function would never return
|
||||
return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
}
|
||||
|
||||
func getControlPipe(path string) (*os.File, error) {
|
||||
if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
|
||||
}
|
||||
|
||||
// Signal sends the provided signal to the process
|
||||
func (p *process) Signal(s os.Signal) error {
|
||||
return syscall.Kill(p.pid, s.(syscall.Signal))
|
||||
}
|
||||
|
||||
// Start unblocks the associated container init process.
|
||||
// This should only be called on the process with ID "init"
|
||||
func (p *process) Start() error {
|
||||
if p.ID() == InitProcessID {
|
||||
var (
|
||||
errC = make(chan error, 1)
|
||||
args = append(p.container.runtimeArgs, "start", p.container.id)
|
||||
cmd = exec.Command(p.container.runtime, args...)
|
||||
)
|
||||
go func() {
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
errC <- fmt.Errorf("%s: %q", err.Error(), out)
|
||||
}
|
||||
errC <- nil
|
||||
}()
|
||||
select {
|
||||
case err := <-errC:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-p.cmdDoneCh:
|
||||
if !p.cmdSuccess {
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
cmd.Wait()
|
||||
return ErrShimExited
|
||||
}
|
||||
err := <-errC
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/docker/containerd/specs"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrContainerExited is returned when access to an exited
|
||||
// container is attempted
|
||||
ErrContainerExited = errors.New("containerd: container has exited")
|
||||
// ErrProcessNotExited is returned when trying to retrieve the exit
|
||||
// status of an alive process
|
||||
ErrProcessNotExited = errors.New("containerd: process has not exited")
|
||||
// ErrContainerNotStarted is returned when a container fails to
|
||||
// start without error from the shim or the OCI runtime
|
||||
ErrContainerNotStarted = errors.New("containerd: container not started")
|
||||
// ErrContainerStartTimeout is returned if a container takes too
|
||||
// long to start
|
||||
ErrContainerStartTimeout = errors.New("containerd: container did not start before the specified timeout")
|
||||
// ErrShimExited is returned if the shim or the contianer's init process
|
||||
// exits before completing
|
||||
ErrShimExited = errors.New("containerd: shim exited before container process was started")
|
||||
|
||||
errNoPidFile = errors.New("containerd: no process pid file found")
|
||||
errInvalidPidInt = errors.New("containerd: process pid is invalid")
|
||||
errNotImplemented = errors.New("containerd: not implemented")
|
||||
)
|
||||
|
||||
const (
|
||||
// ExitFile holds the name of the pipe used to monitor process
|
||||
// exit
|
||||
ExitFile = "exit"
|
||||
// ExitStatusFile holds the name of the file where the container
|
||||
// exit code is to be written
|
||||
ExitStatusFile = "exitStatus"
|
||||
// StateFile holds the name of the file where the container state
|
||||
// is written
|
||||
StateFile = "state.json"
|
||||
// ControlFile holds the name of the pipe used to control the shim
|
||||
ControlFile = "control"
|
||||
// InitProcessID holds the special ID used for the very first
|
||||
// container's process
|
||||
InitProcessID = "init"
|
||||
// StartTimeFile holds the name of the file in which the process
|
||||
// start time is saved
|
||||
StartTimeFile = "starttime"
|
||||
|
||||
// UnknownStatus is the value returned when a process exit
|
||||
// status cannot be determined
|
||||
UnknownStatus = 255
|
||||
)
|
||||
|
||||
// Checkpoint holds information regarding a container checkpoint
|
||||
type Checkpoint struct {
|
||||
// Timestamp is the time that checkpoint happened
|
||||
Created time.Time `json:"created"`
|
||||
// Name is the name of the checkpoint
|
||||
Name string `json:"name"`
|
||||
// TCP checkpoints open tcp connections
|
||||
TCP bool `json:"tcp"`
|
||||
// UnixSockets persists unix sockets in the checkpoint
|
||||
UnixSockets bool `json:"unixSockets"`
|
||||
// Shell persists tty sessions in the checkpoint
|
||||
Shell bool `json:"shell"`
|
||||
// Exit exits the container after the checkpoint is finished
|
||||
Exit bool `json:"exit"`
|
||||
// EmptyNS tells CRIU to omit a specified namespace
|
||||
EmptyNS []string `json:"emptyNS,omitempty"`
|
||||
}
|
||||
|
||||
// PlatformProcessState container platform-specific fields in the ProcessState structure
|
||||
type PlatformProcessState struct {
|
||||
Checkpoint string `json:"checkpoint"`
|
||||
RootUID int `json:"rootUID"`
|
||||
RootGID int `json:"rootGID"`
|
||||
}
|
||||
|
||||
// State represents a container state
|
||||
type State string
|
||||
|
||||
// Resource regroups the various container limits that can be updated
|
||||
type Resource struct {
|
||||
CPUShares int64
|
||||
BlkioWeight uint16
|
||||
CPUPeriod int64
|
||||
CPUQuota int64
|
||||
CpusetCpus string
|
||||
CpusetMems string
|
||||
KernelMemory int64
|
||||
KernelTCPMemory int64
|
||||
Memory int64
|
||||
MemoryReservation int64
|
||||
MemorySwap int64
|
||||
}
|
||||
|
||||
// Possible container states
|
||||
const (
|
||||
Paused = State("paused")
|
||||
Stopped = State("stopped")
|
||||
Running = State("running")
|
||||
)
|
||||
|
||||
type state struct {
|
||||
Bundle string `json:"bundle"`
|
||||
Labels []string `json:"labels"`
|
||||
Stdin string `json:"stdin"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
Runtime string `json:"runtime"`
|
||||
RuntimeArgs []string `json:"runtimeArgs"`
|
||||
Shim string `json:"shim"`
|
||||
NoPivotRoot bool `json:"noPivotRoot"`
|
||||
}
|
||||
|
||||
// ProcessState holds the process OCI specs along with various fields
|
||||
// required by containerd
|
||||
type ProcessState struct {
|
||||
specs.ProcessSpec
|
||||
Exec bool `json:"exec"`
|
||||
Stdin string `json:"containerdStdin"`
|
||||
Stdout string `json:"containerdStdout"`
|
||||
Stderr string `json:"containerdStderr"`
|
||||
RuntimeArgs []string `json:"runtimeArgs"`
|
||||
NoPivotRoot bool `json:"noPivotRoot"`
|
||||
|
||||
PlatformProcessState
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
utils "github.com/docker/containerd/testutils"
|
||||
)
|
||||
|
||||
var (
|
||||
devNull = "/dev/null"
|
||||
stdin io.WriteCloser
|
||||
runtimeTool = flag.String("runtime", "runc", "Runtime to use for this test")
|
||||
)
|
||||
|
||||
// Create containerd state and oci bundles directory
|
||||
func setup() error {
|
||||
if err := os.MkdirAll(utils.StateDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(utils.BundlesRoot, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates the bundleDir with rootfs, io fifo dir and a default spec.
|
||||
// On success, returns the bundlePath
|
||||
func setupBundle(bundleName string) (string, error) {
|
||||
bundlePath := filepath.Join(utils.BundlesRoot, bundleName)
|
||||
if err := os.MkdirAll(bundlePath, 0755); err != nil {
|
||||
fmt.Println("Unable to create bundlePath due to ", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
io := filepath.Join(bundlePath, "io")
|
||||
if err := os.MkdirAll(io, 0755); err != nil {
|
||||
fmt.Println("Unable to create io dir due to ", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := utils.GenerateReferenceSpecs(bundlePath); err != nil {
|
||||
fmt.Println("Unable to generate OCI reference spec: ", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := utils.CreateBusyboxBundle(bundleName); err != nil {
|
||||
fmt.Println("CreateBusyboxBundle error: ", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bundlePath, nil
|
||||
}
|
||||
|
||||
func setupStdio(cwd string, bundlePath string, bundleName string) (Stdio, error) {
|
||||
s := NewStdio(devNull, devNull, devNull)
|
||||
|
||||
pid := "init"
|
||||
for stdName, stdPath := range map[string]*string{
|
||||
"stdin": &s.Stdin,
|
||||
"stdout": &s.Stdout,
|
||||
"stderr": &s.Stderr,
|
||||
} {
|
||||
*stdPath = filepath.Join(cwd, bundlePath, "io", bundleName+"-"+pid+"-"+stdName)
|
||||
if err := syscall.Mkfifo(*stdPath, 0755); err != nil && !os.IsExist(err) {
|
||||
fmt.Println("Mkfifo error: ", err)
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
|
||||
err := attachStdio(s)
|
||||
if err != nil {
|
||||
fmt.Println("attachStdio error: ", err)
|
||||
return s, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func attachStdio(s Stdio) error {
|
||||
stdinf, err := os.OpenFile(s.Stdin, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdin = stdinf
|
||||
stdoutf, err := os.OpenFile(s.Stdout, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(os.Stdout, stdoutf)
|
||||
stderrf, err := os.OpenFile(s.Stderr, syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go io.Copy(os.Stderr, stderrf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func teardownBundle(bundleName string) {
|
||||
containerRoot := filepath.Join(utils.StateDir, bundleName)
|
||||
os.RemoveAll(containerRoot)
|
||||
|
||||
bundlePath := filepath.Join(utils.BundlesRoot, bundleName)
|
||||
os.RemoveAll(bundlePath)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove containerd state and oci bundles directory
|
||||
func teardown() {
|
||||
os.RemoveAll(utils.StateDir)
|
||||
os.RemoveAll(utils.BundlesRoot)
|
||||
}
|
||||
|
||||
func BenchmarkBusyboxSh(b *testing.B) {
|
||||
bundleName := "busybox-sh"
|
||||
|
||||
wd := utils.GetTestOutDir()
|
||||
if err := os.Chdir(wd); err != nil {
|
||||
b.Fatalf("Could not change working directory: %v", err)
|
||||
}
|
||||
|
||||
if err := setup(); err != nil {
|
||||
b.Fatalf("Error setting up test: %v", err)
|
||||
}
|
||||
defer teardown()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
bundlePath, err := setupBundle(bundleName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s, err := setupStdio(wd, bundlePath, bundleName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c, err := New(ContainerOpts{
|
||||
Root: utils.StateDir,
|
||||
ID: bundleName,
|
||||
Bundle: filepath.Join(wd, bundlePath),
|
||||
Runtime: *runtimeTool,
|
||||
Shim: "containerd-shim",
|
||||
Timeout: 15 * time.Second,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
b.Fatalf("Error creating a New container: ", err)
|
||||
}
|
||||
|
||||
benchmarkStartContainer(b, c, s, bundleName)
|
||||
|
||||
teardownBundle(bundleName)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkStartContainer(b *testing.B, c Container, s Stdio, bundleName string) {
|
||||
p, err := c.Start("", s)
|
||||
if err != nil {
|
||||
b.Fatalf("Error starting container %v", err)
|
||||
}
|
||||
|
||||
kill := exec.Command(c.Runtime(), "kill", bundleName, "KILL")
|
||||
kill.Run()
|
||||
|
||||
p.Wait()
|
||||
c.Delete()
|
||||
|
||||
// wait for kill to finish. selected wait time is arbitrary
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
package runtime
|
||||
|
||||
import "time"
|
||||
|
||||
// Stat holds a container statistics
|
||||
type Stat struct {
|
||||
// Timestamp is the time that the statistics where collected
|
||||
Timestamp time.Time
|
||||
CPU CPU `json:"cpu"`
|
||||
Memory Memory `json:"memory"`
|
||||
Pids Pids `json:"pids"`
|
||||
Blkio Blkio `json:"blkio"`
|
||||
Hugetlb map[string]Hugetlb `json:"hugetlb"`
|
||||
}
|
||||
|
||||
// Hugetlb holds information regarding a container huge tlb usage
|
||||
type Hugetlb struct {
|
||||
Usage uint64 `json:"usage,omitempty"`
|
||||
Max uint64 `json:"max,omitempty"`
|
||||
Failcnt uint64 `json:"failcnt"`
|
||||
}
|
||||
|
||||
// BlkioEntry represents a single record for a Blkio stat
|
||||
type BlkioEntry struct {
|
||||
Major uint64 `json:"major,omitempty"`
|
||||
Minor uint64 `json:"minor,omitempty"`
|
||||
Op string `json:"op,omitempty"`
|
||||
Value uint64 `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Blkio regroups all the Blkio related stats
|
||||
type Blkio struct {
|
||||
IoServiceBytesRecursive []BlkioEntry `json:"ioServiceBytesRecursive,omitempty"`
|
||||
IoServicedRecursive []BlkioEntry `json:"ioServicedRecursive,omitempty"`
|
||||
IoQueuedRecursive []BlkioEntry `json:"ioQueueRecursive,omitempty"`
|
||||
IoServiceTimeRecursive []BlkioEntry `json:"ioServiceTimeRecursive,omitempty"`
|
||||
IoWaitTimeRecursive []BlkioEntry `json:"ioWaitTimeRecursive,omitempty"`
|
||||
IoMergedRecursive []BlkioEntry `json:"ioMergedRecursive,omitempty"`
|
||||
IoTimeRecursive []BlkioEntry `json:"ioTimeRecursive,omitempty"`
|
||||
SectorsRecursive []BlkioEntry `json:"sectorsRecursive,omitempty"`
|
||||
}
|
||||
|
||||
// Pids holds the stat of the pid usage of the machine
|
||||
type Pids struct {
|
||||
Current uint64 `json:"current,omitempty"`
|
||||
Limit uint64 `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
// Throttling holds a cpu throttling information
|
||||
type Throttling struct {
|
||||
Periods uint64 `json:"periods,omitempty"`
|
||||
ThrottledPeriods uint64 `json:"throttledPeriods,omitempty"`
|
||||
ThrottledTime uint64 `json:"throttledTime,omitempty"`
|
||||
}
|
||||
|
||||
// CPUUsage holds information regarding cpu usage
|
||||
type CPUUsage struct {
|
||||
// Units: nanoseconds.
|
||||
Total uint64 `json:"total,omitempty"`
|
||||
Percpu []uint64 `json:"percpu,omitempty"`
|
||||
Kernel uint64 `json:"kernel"`
|
||||
User uint64 `json:"user"`
|
||||
}
|
||||
|
||||
// CPU regroups both a CPU usage and throttling information
|
||||
type CPU struct {
|
||||
Usage CPUUsage `json:"usage,omitempty"`
|
||||
Throttling Throttling `json:"throttling,omitempty"`
|
||||
}
|
||||
|
||||
// MemoryEntry regroups statistic about a given type of memory
|
||||
type MemoryEntry struct {
|
||||
Limit uint64 `json:"limit"`
|
||||
Usage uint64 `json:"usage,omitempty"`
|
||||
Max uint64 `json:"max,omitempty"`
|
||||
Failcnt uint64 `json:"failcnt"`
|
||||
}
|
||||
|
||||
// Memory holds information regarding the different type of memories available
|
||||
type Memory struct {
|
||||
Cache uint64 `json:"cache,omitempty"`
|
||||
Usage MemoryEntry `json:"usage,omitempty"`
|
||||
Swap MemoryEntry `json:"swap,omitempty"`
|
||||
Kernel MemoryEntry `json:"kernel,omitempty"`
|
||||
KernelTCP MemoryEntry `json:"kernelTCP,omitempty"`
|
||||
Raw map[string]uint64 `json:"raw,omitempty"`
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package specs
|
||||
|
||||
import oci "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
type (
|
||||
// ProcessSpec aliases the platform process specs
|
||||
ProcessSpec oci.Process
|
||||
// Spec aliases the platform oci spec
|
||||
Spec oci.Spec
|
||||
// Rlimit aliases the platform resource limit
|
||||
Rlimit oci.Rlimit
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
package specs
|
||||
|
||||
import ocs "github.com/opencontainers/specs/specs-go"
|
||||
|
||||
type (
|
||||
ProcessSpec ocs.Process
|
||||
Spec ocs.Spec
|
||||
)
|
|
@ -1,43 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/containerd/runtime"
|
||||
"github.com/docker/containerd/specs"
|
||||
)
|
||||
|
||||
// AddProcessTask holds everything necessary to add a process to a
|
||||
// container
|
||||
type AddProcessTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
PID string
|
||||
Stdout string
|
||||
Stderr string
|
||||
Stdin string
|
||||
ProcessSpec *specs.ProcessSpec
|
||||
StartResponse chan StartResponse
|
||||
}
|
||||
|
||||
func (s *Supervisor) addProcess(t *AddProcessTask) error {
|
||||
ci, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
process, err := ci.container.Exec(t.PID, *t.ProcessSpec, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.monitor.Add(process); err != nil {
|
||||
return err
|
||||
}
|
||||
t.StartResponse <- StartResponse{}
|
||||
s.notifySubscribers(Event{
|
||||
Timestamp: time.Now(),
|
||||
Type: StateStartProcess,
|
||||
PID: t.PID,
|
||||
ID: t.ID,
|
||||
})
|
||||
return nil
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// +build !windows
|
||||
|
||||
package supervisor
|
||||
|
||||
import "github.com/docker/containerd/runtime"
|
||||
|
||||
// CreateCheckpointTask holds needed parameters to create a new checkpoint
|
||||
type CreateCheckpointTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
CheckpointDir string
|
||||
Checkpoint *runtime.Checkpoint
|
||||
}
|
||||
|
||||
func (s *Supervisor) createCheckpoint(t *CreateCheckpointTask) error {
|
||||
i, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
return i.container.Checkpoint(*t.Checkpoint, t.CheckpointDir)
|
||||
}
|
||||
|
||||
// DeleteCheckpointTask holds needed parameters to delete a checkpoint
|
||||
type DeleteCheckpointTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
CheckpointDir string
|
||||
Checkpoint *runtime.Checkpoint
|
||||
}
|
||||
|
||||
func (s *Supervisor) deleteCheckpoint(t *DeleteCheckpointTask) error {
|
||||
i, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
return i.container.DeleteCheckpoint(t.Checkpoint.Name, t.CheckpointDir)
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
// StartTask holds needed parameters to create a new container
|
||||
type StartTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
BundlePath string
|
||||
Stdout string
|
||||
Stderr string
|
||||
Stdin string
|
||||
StartResponse chan StartResponse
|
||||
Labels []string
|
||||
NoPivotRoot bool
|
||||
Checkpoint *runtime.Checkpoint
|
||||
CheckpointDir string
|
||||
Runtime string
|
||||
RuntimeArgs []string
|
||||
}
|
||||
|
||||
func (s *Supervisor) start(t *StartTask) error {
|
||||
rt := s.config.Runtime
|
||||
rtArgs := s.config.RuntimeArgs
|
||||
if t.Runtime != "" {
|
||||
rt = t.Runtime
|
||||
rtArgs = t.RuntimeArgs
|
||||
}
|
||||
container, err := runtime.New(runtime.ContainerOpts{
|
||||
Root: s.config.StateDir,
|
||||
ID: t.ID,
|
||||
Bundle: t.BundlePath,
|
||||
Runtime: rt,
|
||||
RuntimeArgs: rtArgs,
|
||||
Shim: s.config.ShimName,
|
||||
Labels: t.Labels,
|
||||
NoPivotRoot: t.NoPivotRoot,
|
||||
Timeout: s.config.Timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.containers[t.ID] = &containerInfo{
|
||||
container: container,
|
||||
}
|
||||
task := &startTask{
|
||||
Err: t.ErrorCh(),
|
||||
Container: container,
|
||||
StartResponse: t.StartResponse,
|
||||
Stdin: t.Stdin,
|
||||
Stdout: t.Stdout,
|
||||
Stderr: t.Stderr,
|
||||
}
|
||||
if t.Checkpoint != nil {
|
||||
task.CheckpointPath = filepath.Join(t.CheckpointDir, t.Checkpoint.Name)
|
||||
}
|
||||
s.startTasks <- task
|
||||
return errDeferredResponse
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
type platformStartTask struct {
|
||||
}
|
||||
|
||||
// Checkpoint not supported on Solaris
|
||||
func (task *startTask) setTaskCheckpoint(t *StartTask) {
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
// DeleteTask holds needed parameters to remove a container
|
||||
type DeleteTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
Status uint32
|
||||
PID string
|
||||
NoEvent bool
|
||||
Process runtime.Process
|
||||
}
|
||||
|
||||
func (s *Supervisor) delete(t *DeleteTask) error {
|
||||
if i, ok := s.containers[t.ID]; ok {
|
||||
if err := s.deleteContainer(i.container); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: deleting container")
|
||||
}
|
||||
if t.Process != nil {
|
||||
t.Process.Wait()
|
||||
}
|
||||
if !t.NoEvent {
|
||||
s.notifySubscribers(Event{
|
||||
Type: StateExit,
|
||||
Timestamp: time.Now(),
|
||||
ID: t.ID,
|
||||
Status: t.Status,
|
||||
PID: t.PID,
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) deleteContainer(container runtime.Container) error {
|
||||
delete(s.containers, container.ID())
|
||||
return container.Delete()
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrContainerNotFound is returned when the container ID passed
|
||||
// for a given operation is invalid
|
||||
ErrContainerNotFound = errors.New("containerd: container not found")
|
||||
// ErrProcessNotFound is returned when the process ID passed for
|
||||
// a given operation is invalid
|
||||
ErrProcessNotFound = errors.New("containerd: process not found for container")
|
||||
// ErrUnknownContainerStatus is returned when the container status
|
||||
// cannot be determined
|
||||
ErrUnknownContainerStatus = errors.New("containerd: unknown container status ")
|
||||
// ErrUnknownTask is returned when an unknown Task type is
|
||||
// scheduled (should never happen).
|
||||
ErrUnknownTask = errors.New("containerd: unknown task type")
|
||||
|
||||
// Internal errors
|
||||
errShutdown = errors.New("containerd: supervisor is shutdown")
|
||||
errRootNotAbs = errors.New("containerd: rootfs path is not an absolute path")
|
||||
errNoContainerForPid = errors.New("containerd: pid not registered for any container")
|
||||
// internal error where the handler will defer to another for the final response
|
||||
//
|
||||
// TODO: we could probably do a typed error with another error channel for this to make it
|
||||
// less like magic
|
||||
errDeferredResponse = errors.New("containerd: deferred response")
|
||||
)
|
|
@ -1,87 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
// ExitTask holds needed parameters to execute the exit task
|
||||
type ExitTask struct {
|
||||
baseTask
|
||||
Process runtime.Process
|
||||
}
|
||||
|
||||
func (s *Supervisor) exit(t *ExitTask) error {
|
||||
proc := t.Process
|
||||
status, err := proc.ExitStatus()
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"pid": proc.ID(),
|
||||
"id": proc.Container().ID(),
|
||||
"systemPid": proc.SystemPid(),
|
||||
}).Error("containerd: get exit status")
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"pid": proc.ID(),
|
||||
"status": status,
|
||||
"id": proc.Container().ID(),
|
||||
"systemPid": proc.SystemPid(),
|
||||
}).Debug("containerd: process exited")
|
||||
|
||||
// if the process is the the init process of the container then
|
||||
// fire a separate event for this process
|
||||
if proc.ID() != runtime.InitProcessID {
|
||||
ne := &ExecExitTask{
|
||||
ID: proc.Container().ID(),
|
||||
PID: proc.ID(),
|
||||
Status: status,
|
||||
Process: proc,
|
||||
}
|
||||
s.execExit(ne)
|
||||
return nil
|
||||
}
|
||||
container := proc.Container()
|
||||
ne := &DeleteTask{
|
||||
ID: container.ID(),
|
||||
Status: status,
|
||||
PID: proc.ID(),
|
||||
Process: proc,
|
||||
}
|
||||
s.delete(ne)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecExitTask holds needed parameters to execute the exec exit task
|
||||
type ExecExitTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
PID string
|
||||
Status uint32
|
||||
Process runtime.Process
|
||||
}
|
||||
|
||||
func (s *Supervisor) execExit(t *ExecExitTask) error {
|
||||
container := t.Process.Container()
|
||||
// exec process: we remove this process without notifying the main event loop
|
||||
if err := container.RemoveProcess(t.PID); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: find container for pid")
|
||||
}
|
||||
// If the exec spawned children which are still using its IO
|
||||
// waiting here will block until they die or close their IO
|
||||
// descriptors.
|
||||
// Hence, we use a go routine to avoid block all other operations
|
||||
go func() {
|
||||
t.Process.Wait()
|
||||
s.notifySubscribers(Event{
|
||||
Timestamp: time.Now(),
|
||||
ID: t.ID,
|
||||
Type: StateExit,
|
||||
PID: t.PID,
|
||||
Status: t.Status,
|
||||
})
|
||||
}()
|
||||
return nil
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import "github.com/docker/containerd/runtime"
|
||||
|
||||
// GetContainersTask holds needed parameters to retrieve a list of
|
||||
// containers
|
||||
type GetContainersTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
GetState func(c runtime.Container) (interface{}, error)
|
||||
|
||||
Containers []runtime.Container
|
||||
States []interface{}
|
||||
}
|
||||
|
||||
func (s *Supervisor) getContainers(t *GetContainersTask) error {
|
||||
|
||||
if t.ID != "" {
|
||||
ci, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
t.Containers = append(t.Containers, ci.container)
|
||||
if t.GetState != nil {
|
||||
st, err := t.GetState(ci.container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.States = append(t.States, st)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ci := range s.containers {
|
||||
t.Containers = append(t.Containers, ci.container)
|
||||
if t.GetState != nil {
|
||||
st, err := t.GetState(ci.container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.States = append(t.States, st)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// +build !solaris
|
||||
|
||||
package supervisor
|
||||
|
||||
import "github.com/cloudfoundry/gosigar"
|
||||
|
||||
// Machine holds the current machine cpu count and ram size
|
||||
type Machine struct {
|
||||
Cpus int
|
||||
Memory int64
|
||||
}
|
||||
|
||||
// CollectMachineInformation returns information regarding the current
|
||||
// machine (e.g. CPU count, RAM amount)
|
||||
func CollectMachineInformation() (Machine, error) {
|
||||
m := Machine{}
|
||||
cpu := sigar.CpuList{}
|
||||
if err := cpu.Get(); err != nil {
|
||||
return m, err
|
||||
}
|
||||
m.Cpus = len(cpu.List)
|
||||
mem := sigar.Mem{}
|
||||
if err := mem.Get(); err != nil {
|
||||
return m, err
|
||||
}
|
||||
m.Memory = int64(mem.Total / 1024 / 1024)
|
||||
return m, nil
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Machine struct {
|
||||
Cpus int
|
||||
Memory int64
|
||||
}
|
||||
|
||||
func CollectMachineInformation() (Machine, error) {
|
||||
m := Machine{}
|
||||
return m, errors.New("supervisor CollectMachineInformation not implemented on Solaris")
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OOMTask holds needed parameters to report a container OOM
|
||||
type OOMTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
}
|
||||
|
||||
func (s *Supervisor) oom(t *OOMTask) error {
|
||||
logrus.WithField("id", t.ID).Debug("containerd: container oom")
|
||||
s.notifySubscribers(Event{
|
||||
Timestamp: time.Now(),
|
||||
ID: t.ID,
|
||||
Type: StateOOM,
|
||||
})
|
||||
return nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import "os"
|
||||
|
||||
// SignalTask holds needed parameters to signal a container
|
||||
type SignalTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
PID string
|
||||
Signal os.Signal
|
||||
}
|
||||
|
||||
func (s *Supervisor) signal(t *SignalTask) error {
|
||||
i, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
processes, err := i.container.Processes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range processes {
|
||||
if p.ID() == t.PID {
|
||||
return p.Signal(t.Signal)
|
||||
}
|
||||
}
|
||||
return ErrProcessNotFound
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
func sortProcesses(p []runtime.Process) {
|
||||
sort.Sort(&processSorter{p})
|
||||
}
|
||||
|
||||
type processSorter struct {
|
||||
processes []runtime.Process
|
||||
}
|
||||
|
||||
func (s *processSorter) Len() int {
|
||||
return len(s.processes)
|
||||
}
|
||||
|
||||
func (s *processSorter) Swap(i, j int) {
|
||||
s.processes[i], s.processes[j] = s.processes[j], s.processes[i]
|
||||
}
|
||||
|
||||
func (s *processSorter) Less(i, j int) bool {
|
||||
return s.processes[j].ID() == "init"
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/containerd/runtime"
|
||||
"github.com/docker/containerd/specs"
|
||||
)
|
||||
|
||||
var (
|
||||
runtimeTool = flag.String("runtime", "runc", "Runtime to use for this test")
|
||||
)
|
||||
|
||||
type testProcess struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (p *testProcess) ID() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
func (p *testProcess) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testProcess) CloseStdin() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testProcess) Resize(w, h int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testProcess) Stdio() runtime.Stdio {
|
||||
return runtime.Stdio{}
|
||||
}
|
||||
|
||||
func (p *testProcess) SystemPid() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (p *testProcess) ExitFD() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (p *testProcess) ExitStatus() (uint32, error) {
|
||||
return runtime.UnknownStatus, nil
|
||||
}
|
||||
|
||||
func (p *testProcess) Container() runtime.Container {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testProcess) Spec() specs.ProcessSpec {
|
||||
return specs.ProcessSpec{}
|
||||
}
|
||||
|
||||
func (p *testProcess) Signal(os.Signal) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testProcess) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *testProcess) State() runtime.State {
|
||||
return runtime.Running
|
||||
}
|
||||
|
||||
func (p *testProcess) Wait() {
|
||||
}
|
||||
|
||||
func TestSortProcesses(t *testing.T) {
|
||||
p := []runtime.Process{
|
||||
&testProcess{"ls"},
|
||||
&testProcess{"other"},
|
||||
&testProcess{"init"},
|
||||
&testProcess{"other2"},
|
||||
}
|
||||
s := &processSorter{p}
|
||||
sort.Sort(s)
|
||||
|
||||
if id := p[len(p)-1].ID(); id != "init" {
|
||||
t.Fatalf("expected init but received %q", id)
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import "github.com/docker/containerd/runtime"
|
||||
|
||||
// StatsTask holds needed parameters to retrieve a container statistics
|
||||
type StatsTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
Stat chan *runtime.Stat
|
||||
}
|
||||
|
||||
func (s *Supervisor) stats(t *StatsTask) error {
|
||||
i, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
// TODO: use workers for this
|
||||
go func() {
|
||||
s, err := i.container.Stats()
|
||||
if err != nil {
|
||||
t.ErrorCh() <- err
|
||||
return
|
||||
}
|
||||
t.ErrorCh() <- nil
|
||||
t.Stat <- s
|
||||
}()
|
||||
return errDeferredResponse
|
||||
}
|
|
@ -1,310 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/monitor"
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBufferSize = 2048 // size of queue in eventloop
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
StateDir string
|
||||
Runtime string
|
||||
ShimName string
|
||||
RuntimeArgs []string
|
||||
Timeout time.Duration
|
||||
EventRetainCount int
|
||||
}
|
||||
|
||||
// New returns an initialized Process supervisor.
|
||||
func New(c Config) (*Supervisor, error) {
|
||||
startTasks := make(chan *startTask, 10)
|
||||
if err := os.MkdirAll(c.StateDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
machine, err := CollectMachineInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err := monitor.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go m.Run()
|
||||
s := &Supervisor{
|
||||
config: c,
|
||||
containers: make(map[string]*containerInfo),
|
||||
startTasks: startTasks,
|
||||
machine: machine,
|
||||
subscribers: make(map[chan Event]struct{}),
|
||||
tasks: make(chan Task, defaultBufferSize),
|
||||
monitor: m,
|
||||
}
|
||||
if err := setupEventLog(s, c.EventRetainCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go s.monitorEventHandler()
|
||||
if err := s.restore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Supervisor represents a container supervisor
|
||||
type Supervisor struct {
|
||||
config Config
|
||||
containers map[string]*containerInfo
|
||||
startTasks chan *startTask
|
||||
// we need a lock around the subscribers map only because additions and deletions from
|
||||
// the map are via the API so we cannot really control the concurrency
|
||||
subscriberLock sync.RWMutex
|
||||
subscribers map[chan Event]struct{}
|
||||
machine Machine
|
||||
tasks chan Task
|
||||
monitor *monitor.Monitor
|
||||
eventLog []Event
|
||||
eventLock sync.Mutex
|
||||
}
|
||||
|
||||
// Stop closes all startTasks and sends a SIGTERM to each container's pid1 then waits for they to
|
||||
// terminate. After it has handled all the SIGCHILD events it will close the signals chan
|
||||
// and exit. Stop is a non-blocking call and will return after the containers have been signaled
|
||||
func (s *Supervisor) Stop() {
|
||||
// Close the startTasks channel so that no new containers get started
|
||||
close(s.startTasks)
|
||||
}
|
||||
|
||||
// Close closes any open files in the supervisor but expects that Stop has been
|
||||
// callsed so that no more containers are started.
|
||||
func (s *Supervisor) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Event represents a container event
|
||||
type Event struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
PID string `json:"pid,omitempty"`
|
||||
Status uint32 `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Events returns an event channel that external consumers can use to receive updates
|
||||
// on container events
|
||||
func (s *Supervisor) Events(from time.Time, storedOnly bool, id string) chan Event {
|
||||
c := make(chan Event, defaultBufferSize)
|
||||
if storedOnly {
|
||||
defer s.Unsubscribe(c)
|
||||
}
|
||||
s.subscriberLock.Lock()
|
||||
defer s.subscriberLock.Unlock()
|
||||
if !from.IsZero() {
|
||||
// replay old event
|
||||
s.eventLock.Lock()
|
||||
past := s.eventLog[:]
|
||||
s.eventLock.Unlock()
|
||||
for _, e := range past {
|
||||
if e.Timestamp.After(from) {
|
||||
if id == "" || e.ID == id {
|
||||
c <- e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if storedOnly {
|
||||
close(c)
|
||||
} else {
|
||||
s.subscribers[c] = struct{}{}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Unsubscribe removes the provided channel from receiving any more events
|
||||
func (s *Supervisor) Unsubscribe(sub chan Event) {
|
||||
s.subscriberLock.Lock()
|
||||
defer s.subscriberLock.Unlock()
|
||||
if _, ok := s.subscribers[sub]; ok {
|
||||
delete(s.subscribers, sub)
|
||||
close(sub)
|
||||
}
|
||||
}
|
||||
|
||||
// notifySubscribers will send the provided event to the external subscribers
|
||||
// of the events channel
|
||||
func (s *Supervisor) notifySubscribers(e Event) {
|
||||
s.subscriberLock.RLock()
|
||||
defer s.subscriberLock.RUnlock()
|
||||
for sub := range s.subscribers {
|
||||
// do a non-blocking send for the channel
|
||||
select {
|
||||
case sub <- e:
|
||||
default:
|
||||
logrus.WithField("event", e.Type).Warn("containerd: event not sent to subscriber")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start is a non-blocking call that runs the supervisor for monitoring contianer processes and
|
||||
// executing new containers.
|
||||
//
|
||||
// This event loop is the only thing that is allowed to modify state of containers and processes
|
||||
// therefore it is save to do operations in the handlers that modify state of the system or
|
||||
// state of the Supervisor
|
||||
func (s *Supervisor) Start() error {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"stateDir": s.config.StateDir,
|
||||
"runtime": s.config.Runtime,
|
||||
"runtimeArgs": s.config.RuntimeArgs,
|
||||
"memory": s.machine.Memory,
|
||||
"cpus": s.machine.Cpus,
|
||||
}).Debug("containerd: supervisor running")
|
||||
go func() {
|
||||
for i := range s.tasks {
|
||||
s.handleTask(i)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Machine returns the machine information for which the
|
||||
// supervisor is executing on.
|
||||
func (s *Supervisor) Machine() Machine {
|
||||
return s.machine
|
||||
}
|
||||
|
||||
// SendTask sends the provided event the the supervisors main event loop
|
||||
func (s *Supervisor) SendTask(evt Task) {
|
||||
s.tasks <- evt
|
||||
}
|
||||
|
||||
func (s *Supervisor) monitorEventHandler() {
|
||||
for e := range s.monitor.Events() {
|
||||
switch t := e.(type) {
|
||||
case runtime.Process:
|
||||
if err := s.monitor.Remove(e); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: remove process event FD from monitor")
|
||||
}
|
||||
if err := t.Close(); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: close process event FD")
|
||||
}
|
||||
ev := &ExitTask{
|
||||
Process: t,
|
||||
}
|
||||
s.SendTask(ev)
|
||||
case runtime.OOM:
|
||||
if t.Removed() {
|
||||
if err := s.monitor.Remove(e); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: remove oom event FD from monitor")
|
||||
}
|
||||
if err := t.Close(); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: close oom event FD")
|
||||
}
|
||||
// don't send an event on the close of this FD
|
||||
continue
|
||||
}
|
||||
ev := &OOMTask{
|
||||
ID: t.ContainerID(),
|
||||
}
|
||||
s.SendTask(ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Supervisor) restore() error {
|
||||
dirs, err := ioutil.ReadDir(s.config.StateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
id := d.Name()
|
||||
container, err := runtime.Load(s.config.StateDir, id, s.config.ShimName, s.config.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
processes, err := container.Processes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.containers[id] = &containerInfo{
|
||||
container: container,
|
||||
}
|
||||
oom, err := container.OOM()
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: get oom FD")
|
||||
}
|
||||
if err := s.monitor.Add(oom); err != nil && err != runtime.ErrContainerExited {
|
||||
logrus.WithField("error", err).Error("containerd: notify OOM events")
|
||||
}
|
||||
logrus.WithField("id", id).Debug("containerd: container restored")
|
||||
var exitedProcesses []runtime.Process
|
||||
for _, p := range processes {
|
||||
if p.State() == runtime.Running {
|
||||
if err := s.monitor.Add(p); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
exitedProcesses = append(exitedProcesses, p)
|
||||
}
|
||||
}
|
||||
if len(exitedProcesses) > 0 {
|
||||
// sort processes so that init is fired last because that is how the kernel sends the
|
||||
// exit events
|
||||
sortProcesses(exitedProcesses)
|
||||
for _, p := range exitedProcesses {
|
||||
e := &ExitTask{
|
||||
Process: p,
|
||||
}
|
||||
s.SendTask(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) handleTask(i Task) {
|
||||
var err error
|
||||
switch t := i.(type) {
|
||||
case *AddProcessTask:
|
||||
err = s.addProcess(t)
|
||||
case *CreateCheckpointTask:
|
||||
err = s.createCheckpoint(t)
|
||||
case *DeleteCheckpointTask:
|
||||
err = s.deleteCheckpoint(t)
|
||||
case *StartTask:
|
||||
err = s.start(t)
|
||||
case *DeleteTask:
|
||||
err = s.delete(t)
|
||||
case *ExitTask:
|
||||
err = s.exit(t)
|
||||
case *GetContainersTask:
|
||||
err = s.getContainers(t)
|
||||
case *SignalTask:
|
||||
err = s.signal(t)
|
||||
case *StatsTask:
|
||||
err = s.stats(t)
|
||||
case *UpdateTask:
|
||||
err = s.updateContainer(t)
|
||||
case *UpdateProcessTask:
|
||||
err = s.updateProcess(t)
|
||||
case *OOMTask:
|
||||
err = s.oom(t)
|
||||
default:
|
||||
err = ErrUnknownTask
|
||||
}
|
||||
if err != errDeferredResponse {
|
||||
i.ErrorCh() <- err
|
||||
close(i.ErrorCh())
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
// StartResponse is the response containing a started container
|
||||
type StartResponse struct {
|
||||
Container runtime.Container
|
||||
}
|
||||
|
||||
// Task executes an action returning an error chan with either nil or
|
||||
// the error from executing the task
|
||||
type Task interface {
|
||||
// ErrorCh returns a channel used to report and error from an async task
|
||||
ErrorCh() chan error
|
||||
}
|
||||
|
||||
type baseTask struct {
|
||||
errCh chan error
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (t *baseTask) ErrorCh() chan error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.errCh == nil {
|
||||
t.errCh = make(chan error, 1)
|
||||
}
|
||||
return t.errCh
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
// State constants used in Event types
|
||||
const (
|
||||
StateStart = "start-container"
|
||||
StatePause = "pause"
|
||||
StateResume = "resume"
|
||||
StateExit = "exit"
|
||||
StateStartProcess = "start-process"
|
||||
StateOOM = "oom"
|
||||
StateLive = "live"
|
||||
)
|
|
@ -1,95 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
// UpdateTask holds needed parameters to update a container resource constraints
|
||||
type UpdateTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
State runtime.State
|
||||
Resources *runtime.Resource
|
||||
}
|
||||
|
||||
func (s *Supervisor) updateContainer(t *UpdateTask) error {
|
||||
i, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
container := i.container
|
||||
if t.State != "" {
|
||||
switch t.State {
|
||||
case runtime.Running:
|
||||
if err := container.Resume(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.notifySubscribers(Event{
|
||||
ID: t.ID,
|
||||
Type: StateResume,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
case runtime.Paused:
|
||||
if err := container.Pause(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.notifySubscribers(Event{
|
||||
ID: t.ID,
|
||||
Type: StatePause,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
default:
|
||||
return ErrUnknownContainerStatus
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if t.Resources != nil {
|
||||
return container.UpdateResources(t.Resources)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProcessTask holds needed parameters to update a container
|
||||
// process terminal size or close its stdin
|
||||
type UpdateProcessTask struct {
|
||||
baseTask
|
||||
ID string
|
||||
PID string
|
||||
CloseStdin bool
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func (s *Supervisor) updateProcess(t *UpdateProcessTask) error {
|
||||
i, ok := s.containers[t.ID]
|
||||
if !ok {
|
||||
return ErrContainerNotFound
|
||||
}
|
||||
processes, err := i.container.Processes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var process runtime.Process
|
||||
for _, p := range processes {
|
||||
if p.ID() == t.PID {
|
||||
process = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if process == nil {
|
||||
return ErrProcessNotFound
|
||||
}
|
||||
if t.CloseStdin {
|
||||
if err := process.CloseStdin(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if t.Width > 0 || t.Height > 0 {
|
||||
if err := process.Resize(t.Width, t.Height); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
type containerInfo struct {
|
||||
container runtime.Container
|
||||
}
|
||||
|
||||
func setupEventLog(s *Supervisor, retainCount int) error {
|
||||
if err := readEventLog(s); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.WithField("count", len(s.eventLog)).Debug("containerd: read past events")
|
||||
events := s.Events(time.Time{}, false, "")
|
||||
return eventLogger(s, filepath.Join(s.config.StateDir, "events.log"), events, retainCount)
|
||||
}
|
||||
|
||||
func eventLogger(s *Supervisor, path string, events chan Event, retainCount int) error {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
var (
|
||||
count = len(s.eventLog)
|
||||
enc = json.NewEncoder(f)
|
||||
)
|
||||
for e := range events {
|
||||
// if we have a specified retain count make sure the truncate the event
|
||||
// log if it grows past the specified number of events to keep.
|
||||
if retainCount > 0 {
|
||||
if count > retainCount {
|
||||
logrus.Debug("truncating event log")
|
||||
// close the log file
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
slice := retainCount - 1
|
||||
l := len(s.eventLog)
|
||||
if slice >= l {
|
||||
slice = l
|
||||
}
|
||||
s.eventLock.Lock()
|
||||
s.eventLog = s.eventLog[len(s.eventLog)-slice:]
|
||||
s.eventLock.Unlock()
|
||||
if f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0755); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: open event to journal")
|
||||
continue
|
||||
}
|
||||
enc = json.NewEncoder(f)
|
||||
count = 0
|
||||
for _, le := range s.eventLog {
|
||||
if err := enc.Encode(le); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: write event to journal")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.eventLock.Lock()
|
||||
s.eventLog = append(s.eventLog, e)
|
||||
s.eventLock.Unlock()
|
||||
count++
|
||||
if err := enc.Encode(e); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: write event to journal")
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func readEventLog(s *Supervisor) error {
|
||||
f, err := os.Open(filepath.Join(s.config.StateDir, "events.log"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
dec := json.NewDecoder(f)
|
||||
for {
|
||||
var e Event
|
||||
if err := dec.Decode(&e); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.eventLog = append(s.eventLog, e)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
package supervisor
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/containerd/runtime"
|
||||
)
|
||||
|
||||
// Worker interface
|
||||
type Worker interface {
|
||||
Start()
|
||||
}
|
||||
|
||||
type startTask struct {
|
||||
Container runtime.Container
|
||||
CheckpointPath string
|
||||
Stdin string
|
||||
Stdout string
|
||||
Stderr string
|
||||
Err chan error
|
||||
StartResponse chan StartResponse
|
||||
}
|
||||
|
||||
// NewWorker return a new initialized worker
|
||||
func NewWorker(s *Supervisor, wg *sync.WaitGroup) Worker {
|
||||
return &worker{
|
||||
s: s,
|
||||
wg: wg,
|
||||
}
|
||||
}
|
||||
|
||||
type worker struct {
|
||||
wg *sync.WaitGroup
|
||||
s *Supervisor
|
||||
}
|
||||
|
||||
// Start runs a loop in charge of starting new containers
|
||||
func (w *worker) Start() {
|
||||
defer w.wg.Done()
|
||||
for t := range w.s.startTasks {
|
||||
process, err := t.Container.Start(t.CheckpointPath, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr))
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"id": t.Container.ID(),
|
||||
}).Error("containerd: start container")
|
||||
t.Err <- err
|
||||
evt := &DeleteTask{
|
||||
ID: t.Container.ID(),
|
||||
NoEvent: true,
|
||||
Process: process,
|
||||
}
|
||||
w.s.SendTask(evt)
|
||||
continue
|
||||
}
|
||||
oom, err := t.Container.OOM()
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: get oom FD")
|
||||
}
|
||||
if err := w.s.monitor.Add(oom); err != nil && err != runtime.ErrContainerExited {
|
||||
if process.State() != runtime.Stopped {
|
||||
logrus.WithField("error", err).Error("containerd: notify OOM events")
|
||||
}
|
||||
}
|
||||
if err := w.s.monitor.Add(process); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: add process to monitor")
|
||||
t.Err <- err
|
||||
evt := &DeleteTask{
|
||||
ID: t.Container.ID(),
|
||||
NoEvent: true,
|
||||
Process: process,
|
||||
}
|
||||
w.s.SendTask(evt)
|
||||
continue
|
||||
}
|
||||
// only call process start if we aren't restoring from a checkpoint
|
||||
// if we have restored from a checkpoint then the process is already started
|
||||
if t.CheckpointPath == "" {
|
||||
if err := process.Start(); err != nil {
|
||||
logrus.WithField("error", err).Error("containerd: start init process")
|
||||
t.Err <- err
|
||||
evt := &DeleteTask{
|
||||
ID: t.Container.ID(),
|
||||
NoEvent: true,
|
||||
Process: process,
|
||||
}
|
||||
w.s.SendTask(evt)
|
||||
continue
|
||||
}
|
||||
}
|
||||
t.Err <- nil
|
||||
t.StartResponse <- StartResponse{
|
||||
Container: t.Container,
|
||||
}
|
||||
w.s.notifySubscribers(Event{
|
||||
Timestamp: time.Now(),
|
||||
ID: t.Container.ID(),
|
||||
Type: StateStart,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetTestOutDir returns the output directory for testing and benchmark artifacts
|
||||
func GetTestOutDir() string {
|
||||
out, _ := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
|
||||
repoRoot := string(out)
|
||||
prefix := filepath.Join(strings.TrimSpace(repoRoot), "output")
|
||||
return prefix
|
||||
}
|
||||
|
||||
var (
|
||||
// ArchivesDir holds the location of the available rootfs
|
||||
ArchivesDir = filepath.Join("test-artifacts", "archives")
|
||||
// BundlesRoot holds the location where OCI Bundles are stored
|
||||
BundlesRoot = filepath.Join("test-artifacts", "oci-bundles")
|
||||
// OutputDirFormat holds the standard format used when creating a
|
||||
// new test output directory
|
||||
OutputDirFormat = filepath.Join("test-artifacts", "runs", "%s")
|
||||
// RefOciSpecsPath holds the path to the generic OCI config
|
||||
RefOciSpecsPath = filepath.Join(BundlesRoot, "config.json")
|
||||
// StateDir holds the path to the directory used by the containerd
|
||||
// started by tests
|
||||
StateDir = "/run/containerd-bench-test"
|
||||
)
|
||||
|
||||
// untarRootfs untars the given `source` tarPath into `destination/rootfs`
|
||||
func untarRootfs(source string, destination string) error {
|
||||
rootfs := filepath.Join(destination, "rootfs")
|
||||
|
||||
if err := os.MkdirAll(rootfs, 0755); err != nil {
|
||||
fmt.Println("untarRootfs os.MkdirAll failed with err %v", err)
|
||||
return nil
|
||||
}
|
||||
tar := exec.Command("tar", "-C", rootfs, "-xf", source)
|
||||
return tar.Run()
|
||||
}
|
||||
|
||||
// GenerateReferenceSpecs generates a default OCI specs via `runc spec`
|
||||
func GenerateReferenceSpecs(destination string) error {
|
||||
if _, err := os.Stat(filepath.Join(destination, "config.json")); err == nil {
|
||||
return nil
|
||||
}
|
||||
specs := exec.Command("runc", "spec")
|
||||
specs.Dir = destination
|
||||
return specs.Run()
|
||||
}
|
||||
|
||||
// CreateBundle generates a valid OCI bundle from the given rootfs
|
||||
func CreateBundle(source, name string) error {
|
||||
bundlePath := filepath.Join(BundlesRoot, name)
|
||||
|
||||
if err := untarRootfs(filepath.Join(ArchivesDir, source+".tar"), bundlePath); err != nil {
|
||||
return fmt.Errorf("Failed to untar %s.tar: %v", source, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateBusyboxBundle generates a bundle based on the busybox rootfs
|
||||
func CreateBusyboxBundle(name string) error {
|
||||
return CreateBundle("busybox", name)
|
||||
}
|
Loading…
Reference in a new issue