diff --git a/.gitignore b/.gitignore index 07b5e24..4c16aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ containerd/containerd +containerd-shim/containerd-shim bin/ ctr/ctr hack/benchmark diff --git a/Makefile b/Makefile index 21619a0..a3adac9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -BUILDTAGS=libcontainer +BUILDTAGS= # if this session isn't interactive, then we don't want to allocate a # TTY, which would fail, but if it is interactive, we do want to attach @@ -13,7 +13,7 @@ DOCKER_RUN := docker run --rm -i $(DOCKER_FLAGS) "$(DOCKER_IMAGE)" export GOPATH:=$(CURDIR)/vendor:$(GOPATH) -all: client daemon +all: client daemon shim bin: mkdir -p bin/ @@ -27,6 +27,9 @@ client: bin daemon: bin cd containerd && go build -tags "$(BUILDTAGS)" -o ../bin/containerd +shim: bin + cd containerd-shim && go build -tags "$(BUILDTAGS)" -o ../bin/containerd-shim + dbuild: @docker build --rm --force-rm -t "$(DOCKER_IMAGE)" . diff --git a/api/grpc/server/server.go b/api/grpc/server/server.go index 430e00c..61685f6 100644 --- a/api/grpc/server/server.go +++ b/api/grpc/server/server.go @@ -2,15 +2,18 @@ package server import ( "errors" + "fmt" "syscall" + "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "github.com/Sirupsen/logrus" "github.com/docker/containerd/api/grpc/types" "github.com/docker/containerd/runtime" "github.com/docker/containerd/supervisor" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/specs" "golang.org/x/net/context" ) @@ -30,35 +33,39 @@ func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContaine if c.BundlePath == "" { return nil, errors.New("empty bundle path") } - e := supervisor.NewEvent(supervisor.StartContainerEventType) + e := supervisor.NewTask(supervisor.StartContainerTaskType) e.ID = c.Id e.BundlePath = c.BundlePath + e.Stdin = c.Stdin e.Stdout = c.Stdout e.Stderr = c.Stderr - e.Stdin = c.Stdin - e.Console = c.Console + e.Labels = c.Labels e.StartResponse = make(chan supervisor.StartResponse, 1) if c.Checkpoint != "" { e.Checkpoint = &runtime.Checkpoint{ Name: c.Checkpoint, } } - s.sv.SendEvent(e) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } - sr := <-e.StartResponse + r := <-e.StartResponse + apiC, err := createAPIContainer(r.Container, false) + if err != nil { + return nil, err + } return &types.CreateContainerResponse{ - Pid: uint32(sr.Pid), + Container: apiC, }, nil } func (s *apiServer) Signal(ctx context.Context, r *types.SignalRequest) (*types.SignalResponse, error) { - e := supervisor.NewEvent(supervisor.SignalEventType) + e := supervisor.NewTask(supervisor.SignalTaskType) e.ID = r.Id - e.Pid = int(r.Pid) + e.Pid = r.Pid e.Signal = syscall.Signal(int(r.Signal)) - s.sv.SendEvent(e) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } @@ -77,22 +84,30 @@ func (s *apiServer) AddProcess(ctx context.Context, r *types.AddProcessRequest) AdditionalGids: r.User.AdditionalGids, }, } - e := supervisor.NewEvent(supervisor.AddProcessEventType) + 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.NewTask(supervisor.AddProcessTaskType) e.ID = r.Id - e.Process = process - e.Console = r.Console + e.Pid = r.Pid + e.ProcessSpec = process e.Stdin = r.Stdin e.Stdout = r.Stdout e.Stderr = r.Stderr - s.sv.SendEvent(e) + e.StartResponse = make(chan supervisor.StartResponse, 1) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } - return &types.AddProcessResponse{Pid: uint32(e.Pid)}, nil + <-e.StartResponse + return &types.AddProcessResponse{}, nil } func (s *apiServer) CreateCheckpoint(ctx context.Context, r *types.CreateCheckpointRequest) (*types.CreateCheckpointResponse, error) { - e := supervisor.NewEvent(supervisor.CreateCheckpointEventType) + e := supervisor.NewTask(supervisor.CreateCheckpointTaskType) e.ID = r.Id e.Checkpoint = &runtime.Checkpoint{ Name: r.Checkpoint.Name, @@ -101,7 +116,7 @@ func (s *apiServer) CreateCheckpoint(ctx context.Context, r *types.CreateCheckpo UnixSockets: r.Checkpoint.UnixSockets, Shell: r.Checkpoint.Shell, } - s.sv.SendEvent(e) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } @@ -112,12 +127,12 @@ func (s *apiServer) DeleteCheckpoint(ctx context.Context, r *types.DeleteCheckpo if r.Name == "" { return nil, errors.New("checkpoint name cannot be empty") } - e := supervisor.NewEvent(supervisor.DeleteCheckpointEventType) + e := supervisor.NewTask(supervisor.DeleteCheckpointTaskType) e.ID = r.Id e.Checkpoint = &runtime.Checkpoint{ Name: r.Name, } - s.sv.SendEvent(e) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } @@ -125,8 +140,8 @@ func (s *apiServer) DeleteCheckpoint(ctx context.Context, r *types.DeleteCheckpo } func (s *apiServer) ListCheckpoint(ctx context.Context, r *types.ListCheckpointRequest) (*types.ListCheckpointResponse, error) { - e := supervisor.NewEvent(supervisor.GetContainerEventType) - s.sv.SendEvent(e) + e := supervisor.NewTask(supervisor.GetContainerTaskType) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } @@ -140,11 +155,11 @@ func (s *apiServer) ListCheckpoint(ctx context.Context, r *types.ListCheckpointR if container == nil { return nil, grpc.Errorf(codes.NotFound, "no such containers") } + var out []*types.Checkpoint checkpoints, err := container.Checkpoints() if err != nil { return nil, err } - var out []*types.Checkpoint for _, c := range checkpoints { out = append(out, &types.Checkpoint{ Name: c.Name, @@ -159,129 +174,206 @@ func (s *apiServer) ListCheckpoint(ctx context.Context, r *types.ListCheckpointR } func (s *apiServer) State(ctx context.Context, r *types.StateRequest) (*types.StateResponse, error) { - e := supervisor.NewEvent(supervisor.GetContainerEventType) - s.sv.SendEvent(e) + e := supervisor.NewTask(supervisor.GetContainerTaskType) + e.ID = r.Id + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } m := s.sv.Machine() state := &types.StateResponse{ Machine: &types.Machine{ - Id: m.ID, Cpus: uint32(m.Cpus), - Memory: uint64(m.Cpus), + Memory: uint64(m.Memory), }, } for _, c := range e.Containers { - processes, err := c.Processes() + apiC, err := createAPIContainer(c, true) if err != nil { - return nil, grpc.Errorf(codes.Internal, "get processes for container") + return nil, err } - var procs []*types.Process - for _, p := range processes { - pid, err := p.Pid() - if err != nil { - logrus.WithField("error", err).Error("get process pid") - } - oldProc := p.Spec() - procs = append(procs, &types.Process{ - Pid: uint32(pid), - Terminal: oldProc.Terminal, - Args: oldProc.Args, - Env: oldProc.Env, - Cwd: oldProc.Cwd, - User: &types.User{ - Uid: oldProc.User.UID, - Gid: oldProc.User.GID, - AdditionalGids: oldProc.User.AdditionalGids, - }, - }) - } - state.Containers = append(state.Containers, &types.Container{ - Id: c.ID(), - BundlePath: c.Path(), - Processes: procs, - Status: string(c.State()), - }) + state.Containers = append(state.Containers, apiC) } return state, nil } -func (s *apiServer) UpdateContainer(ctx context.Context, r *types.UpdateContainerRequest) (*types.UpdateContainerResponse, error) { - e := supervisor.NewEvent(supervisor.UpdateContainerEventType) - e.ID = r.Id - if r.Signal != 0 { - e.Signal = syscall.Signal(r.Signal) +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") } + var procs []*types.Process + for _, p := range processes { + oldProc := p.Spec() + stdio := p.Stdio() + procs = append(procs, &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, + User: &types.User{ + Uid: oldProc.User.UID, + Gid: oldProc.User.GID, + AdditionalGids: oldProc.User.AdditionalGids, + }, + }) + } + var pids []int + if getPids { + if pids, err = c.Pids(); err != nil { + return nil, grpc.Errorf(codes.Internal, "get all pids for container") + } + } + return &types.Container{ + Id: c.ID(), + BundlePath: c.Path(), + Processes: procs, + Labels: c.Labels(), + Status: string(c.State()), + Pids: toUint32(pids), + }, 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.NewTask(supervisor.UpdateContainerTaskType) + e.ID = r.Id e.State = runtime.State(r.Status) - s.sv.SendEvent(e) + s.sv.SendTask(e) if err := <-e.Err; err != nil { return nil, err } return &types.UpdateContainerResponse{}, nil } -func (s *apiServer) Events(r *types.EventsRequest, stream types.API_EventsServer) error { - events := s.sv.Events() - defer s.sv.Unsubscribe(events) - for evt := range events { - var ev *types.Event - switch evt.Type { - case supervisor.ExitEventType, supervisor.ExecExitEventType: - ev = &types.Event{ - Type: "exit", - Id: evt.ID, - Pid: uint32(evt.Pid), - Status: uint32(evt.Status), - } - case supervisor.OOMEventType: - ev = &types.Event{ - Type: "oom", - Id: evt.ID, - } - } - if ev != nil { - if err := stream.Send(ev); err != nil { - return err - } - } - - } - return nil -} - -func (s *apiServer) GetStats(r *types.StatsRequest, stream types.API_GetStatsServer) error { - e := supervisor.NewEvent(supervisor.StatsEventType) +func (s *apiServer) UpdateProcess(ctx context.Context, r *types.UpdateProcessRequest) (*types.UpdateProcessResponse, error) { + e := supervisor.NewTask(supervisor.UpdateProcessTaskType) e.ID = r.Id - s.sv.SendEvent(e) + 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.Err; err != nil { - if err == supervisor.ErrContainerNotFound { - return grpc.Errorf(codes.NotFound, err.Error()) - } - return err + return nil, err } - defer func() { - ue := supervisor.NewEvent(supervisor.UnsubscribeStatsEventType) - ue.ID = e.ID - ue.Stats = e.Stats - s.sv.SendEvent(ue) - if err := <-ue.Err; err != nil { - logrus.Errorf("Error unsubscribing %s: %v", r.Id, err) - } - }() - for { - select { - case st := <-e.Stats: - pbSt, ok := st.(*types.Stats) - if !ok { - panic("invalid stats type from collector") - } - if err := stream.Send(pbSt); err != nil { - return err - } - case <-stream.Context().Done(): - return nil + return &types.UpdateProcessResponse{}, nil +} + +func (s *apiServer) Events(r *types.EventsRequest, stream types.API_EventsServer) error { + t := time.Time{} + if r.Timestamp != 0 { + t = time.Unix(int64(r.Timestamp), 0) + } + events := s.sv.Events(t) + defer s.sv.Unsubscribe(events) + for e := range events { + if err := stream.Send(&types.Event{ + Id: e.ID, + Type: e.Type, + Timestamp: uint64(e.Timestamp.Unix()), + Pid: e.Pid, + Status: uint32(e.Status), + }); err != nil { + return err } } return nil } + +func (s *apiServer) Stats(ctx context.Context, r *types.StatsRequest) (*types.StatsResponse, error) { + e := supervisor.NewTask(supervisor.StatsTaskType) + e.ID = r.Id + e.Stat = make(chan *runtime.Stat, 1) + s.sv.SendTask(e) + if err := <-e.Err; err != nil { + return nil, err + } + stats := <-e.Stat + t := convertToPb(stats) + return t, nil +} + +func convertToPb(st *runtime.Stat) *types.StatsResponse { + pbSt := &types.StatsResponse{ + Timestamp: uint64(st.Timestamp.Unix()), + CgroupStats: &types.CgroupStats{}, + } + lcSt, ok := st.Data.(*libcontainer.Stats) + if !ok { + return pbSt + } + cpuSt := lcSt.CgroupStats.CpuStats + pbSt.CgroupStats.CpuStats = &types.CpuStats{ + CpuUsage: &types.CpuUsage{ + TotalUsage: cpuSt.CpuUsage.TotalUsage, + PercpuUsage: cpuSt.CpuUsage.PercpuUsage, + UsageInKernelmode: cpuSt.CpuUsage.UsageInKernelmode, + UsageInUsermode: cpuSt.CpuUsage.UsageInUsermode, + }, + ThrottlingData: &types.ThrottlingData{ + Periods: cpuSt.ThrottlingData.Periods, + ThrottledPeriods: cpuSt.ThrottlingData.ThrottledPeriods, + ThrottledTime: cpuSt.ThrottlingData.ThrottledTime, + }, + } + memSt := lcSt.CgroupStats.MemoryStats + pbSt.CgroupStats.MemoryStats = &types.MemoryStats{ + Cache: memSt.Cache, + Usage: &types.MemoryData{ + Usage: memSt.Usage.Usage, + MaxUsage: memSt.Usage.MaxUsage, + Failcnt: memSt.Usage.Failcnt, + }, + SwapUsage: &types.MemoryData{ + Usage: memSt.SwapUsage.Usage, + MaxUsage: memSt.SwapUsage.MaxUsage, + Failcnt: memSt.SwapUsage.Failcnt, + }, + } + blkSt := lcSt.CgroupStats.BlkioStats + pbSt.CgroupStats.BlkioStats = &types.BlkioStats{ + IoServiceBytesRecursive: convertBlkioEntryToPb(blkSt.IoServiceBytesRecursive), + IoServicedRecursive: convertBlkioEntryToPb(blkSt.IoServicedRecursive), + IoQueuedRecursive: convertBlkioEntryToPb(blkSt.IoQueuedRecursive), + IoServiceTimeRecursive: convertBlkioEntryToPb(blkSt.IoServiceTimeRecursive), + IoWaitTimeRecursive: convertBlkioEntryToPb(blkSt.IoWaitTimeRecursive), + IoMergedRecursive: convertBlkioEntryToPb(blkSt.IoMergedRecursive), + IoTimeRecursive: convertBlkioEntryToPb(blkSt.IoTimeRecursive), + SectorsRecursive: convertBlkioEntryToPb(blkSt.SectorsRecursive), + } + pbSt.CgroupStats.HugetlbStats = make(map[string]*types.HugetlbStats) + for k, st := range lcSt.CgroupStats.HugetlbStats { + pbSt.CgroupStats.HugetlbStats[k] = &types.HugetlbStats{ + Usage: st.Usage, + MaxUsage: st.MaxUsage, + Failcnt: st.Failcnt, + } + } + return pbSt +} + +func convertBlkioEntryToPb(b []cgroups.BlkioStatEntry) []*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 +} diff --git a/api/grpc/types/api.pb.go b/api/grpc/types/api.pb.go index 0455a8c..b3af682 100644 --- a/api/grpc/types/api.pb.go +++ b/api/grpc/types/api.pb.go @@ -9,6 +9,8 @@ It is generated from these files: api.proto It has these top-level messages: + UpdateProcessRequest + UpdateProcessResponse CreateContainerRequest CreateContainerResponse SignalRequest @@ -43,7 +45,7 @@ It has these top-level messages: BlkioStats HugetlbStats CgroupStats - Stats + StatsResponse StatsRequest */ package types @@ -62,40 +64,68 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -type CreateContainerRequest struct { +type UpdateProcessRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - BundlePath string `protobuf:"bytes,2,opt,name=bundlePath" json:"bundlePath,omitempty"` - Stdin string `protobuf:"bytes,3,opt,name=stdin" json:"stdin,omitempty"` - Stdout string `protobuf:"bytes,4,opt,name=stdout" json:"stdout,omitempty"` - Stderr string `protobuf:"bytes,5,opt,name=stderr" json:"stderr,omitempty"` - Console string `protobuf:"bytes,6,opt,name=console" json:"console,omitempty"` - Checkpoint string `protobuf:"bytes,7,opt,name=checkpoint" json:"checkpoint,omitempty"` + Pid string `protobuf:"bytes,2,opt,name=pid" json:"pid,omitempty"` + CloseStdin bool `protobuf:"varint,3,opt,name=closeStdin" json:"closeStdin,omitempty"` + Width uint32 `protobuf:"varint,4,opt,name=width" json:"width,omitempty"` + Height uint32 `protobuf:"varint,5,opt,name=height" json:"height,omitempty"` +} + +func (m *UpdateProcessRequest) Reset() { *m = UpdateProcessRequest{} } +func (m *UpdateProcessRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateProcessRequest) ProtoMessage() {} +func (*UpdateProcessRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +type UpdateProcessResponse struct { +} + +func (m *UpdateProcessResponse) Reset() { *m = UpdateProcessResponse{} } +func (m *UpdateProcessResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateProcessResponse) ProtoMessage() {} +func (*UpdateProcessResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +type CreateContainerRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + BundlePath string `protobuf:"bytes,2,opt,name=bundlePath" json:"bundlePath,omitempty"` + Checkpoint string `protobuf:"bytes,3,opt,name=checkpoint" json:"checkpoint,omitempty"` + Stdin string `protobuf:"bytes,4,opt,name=stdin" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,5,opt,name=stdout" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,6,opt,name=stderr" json:"stderr,omitempty"` + Labels []string `protobuf:"bytes,7,rep,name=labels" json:"labels,omitempty"` } func (m *CreateContainerRequest) Reset() { *m = CreateContainerRequest{} } func (m *CreateContainerRequest) String() string { return proto.CompactTextString(m) } func (*CreateContainerRequest) ProtoMessage() {} -func (*CreateContainerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (*CreateContainerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } type CreateContainerResponse struct { - Pid uint32 `protobuf:"varint,1,opt,name=pid" json:"pid,omitempty"` + Container *Container `protobuf:"bytes,1,opt,name=container" json:"container,omitempty"` } func (m *CreateContainerResponse) Reset() { *m = CreateContainerResponse{} } func (m *CreateContainerResponse) String() string { return proto.CompactTextString(m) } func (*CreateContainerResponse) ProtoMessage() {} -func (*CreateContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (*CreateContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *CreateContainerResponse) GetContainer() *Container { + if m != nil { + return m.Container + } + return nil +} type SignalRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Pid uint32 `protobuf:"varint,2,opt,name=pid" json:"pid,omitempty"` + Pid string `protobuf:"bytes,2,opt,name=pid" json:"pid,omitempty"` Signal uint32 `protobuf:"varint,3,opt,name=signal" json:"signal,omitempty"` } func (m *SignalRequest) Reset() { *m = SignalRequest{} } func (m *SignalRequest) String() string { return proto.CompactTextString(m) } func (*SignalRequest) ProtoMessage() {} -func (*SignalRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } +func (*SignalRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } type SignalResponse struct { } @@ -103,7 +133,7 @@ type SignalResponse struct { func (m *SignalResponse) Reset() { *m = SignalResponse{} } func (m *SignalResponse) String() string { return proto.CompactTextString(m) } func (*SignalResponse) ProtoMessage() {} -func (*SignalResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } +func (*SignalResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } type AddProcessRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` @@ -112,16 +142,16 @@ type AddProcessRequest struct { Args []string `protobuf:"bytes,4,rep,name=args" json:"args,omitempty"` Env []string `protobuf:"bytes,5,rep,name=env" json:"env,omitempty"` Cwd string `protobuf:"bytes,6,opt,name=cwd" json:"cwd,omitempty"` - Stdin string `protobuf:"bytes,7,opt,name=stdin" json:"stdin,omitempty"` - Stdout string `protobuf:"bytes,8,opt,name=stdout" json:"stdout,omitempty"` - Stderr string `protobuf:"bytes,9,opt,name=stderr" json:"stderr,omitempty"` - Console string `protobuf:"bytes,10,opt,name=console" json:"console,omitempty"` + Pid string `protobuf:"bytes,7,opt,name=pid" json:"pid,omitempty"` + Stdin string `protobuf:"bytes,8,opt,name=stdin" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,9,opt,name=stdout" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,10,opt,name=stderr" json:"stderr,omitempty"` } func (m *AddProcessRequest) Reset() { *m = AddProcessRequest{} } func (m *AddProcessRequest) String() string { return proto.CompactTextString(m) } func (*AddProcessRequest) ProtoMessage() {} -func (*AddProcessRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } +func (*AddProcessRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } func (m *AddProcessRequest) GetUser() *User { if m != nil { @@ -139,16 +169,15 @@ type User struct { func (m *User) Reset() { *m = User{} } func (m *User) String() string { return proto.CompactTextString(m) } func (*User) ProtoMessage() {} -func (*User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } +func (*User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } type AddProcessResponse struct { - Pid uint32 `protobuf:"varint,1,opt,name=pid" json:"pid,omitempty"` } func (m *AddProcessResponse) Reset() { *m = AddProcessResponse{} } func (m *AddProcessResponse) String() string { return proto.CompactTextString(m) } func (*AddProcessResponse) ProtoMessage() {} -func (*AddProcessResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } +func (*AddProcessResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } type CreateCheckpointRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` @@ -158,7 +187,7 @@ type CreateCheckpointRequest struct { func (m *CreateCheckpointRequest) Reset() { *m = CreateCheckpointRequest{} } func (m *CreateCheckpointRequest) String() string { return proto.CompactTextString(m) } func (*CreateCheckpointRequest) ProtoMessage() {} -func (*CreateCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } +func (*CreateCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } func (m *CreateCheckpointRequest) GetCheckpoint() *Checkpoint { if m != nil { @@ -173,7 +202,7 @@ type CreateCheckpointResponse struct { func (m *CreateCheckpointResponse) Reset() { *m = CreateCheckpointResponse{} } func (m *CreateCheckpointResponse) String() string { return proto.CompactTextString(m) } func (*CreateCheckpointResponse) ProtoMessage() {} -func (*CreateCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } +func (*CreateCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } type DeleteCheckpointRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` @@ -183,7 +212,7 @@ type DeleteCheckpointRequest struct { func (m *DeleteCheckpointRequest) Reset() { *m = DeleteCheckpointRequest{} } func (m *DeleteCheckpointRequest) String() string { return proto.CompactTextString(m) } func (*DeleteCheckpointRequest) ProtoMessage() {} -func (*DeleteCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } +func (*DeleteCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } type DeleteCheckpointResponse struct { } @@ -191,7 +220,7 @@ type DeleteCheckpointResponse struct { func (m *DeleteCheckpointResponse) Reset() { *m = DeleteCheckpointResponse{} } func (m *DeleteCheckpointResponse) String() string { return proto.CompactTextString(m) } func (*DeleteCheckpointResponse) ProtoMessage() {} -func (*DeleteCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } +func (*DeleteCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } type ListCheckpointRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` @@ -200,7 +229,7 @@ type ListCheckpointRequest struct { func (m *ListCheckpointRequest) Reset() { *m = ListCheckpointRequest{} } func (m *ListCheckpointRequest) String() string { return proto.CompactTextString(m) } func (*ListCheckpointRequest) ProtoMessage() {} -func (*ListCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } +func (*ListCheckpointRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } type Checkpoint struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` @@ -213,7 +242,7 @@ type Checkpoint struct { func (m *Checkpoint) Reset() { *m = Checkpoint{} } func (m *Checkpoint) String() string { return proto.CompactTextString(m) } func (*Checkpoint) ProtoMessage() {} -func (*Checkpoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } +func (*Checkpoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } type ListCheckpointResponse struct { Checkpoints []*Checkpoint `protobuf:"bytes,1,rep,name=checkpoints" json:"checkpoints,omitempty"` @@ -222,7 +251,7 @@ type ListCheckpointResponse struct { func (m *ListCheckpointResponse) Reset() { *m = ListCheckpointResponse{} } func (m *ListCheckpointResponse) String() string { return proto.CompactTextString(m) } func (*ListCheckpointResponse) ProtoMessage() {} -func (*ListCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } +func (*ListCheckpointResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } func (m *ListCheckpointResponse) GetCheckpoints() []*Checkpoint { if m != nil { @@ -232,12 +261,13 @@ func (m *ListCheckpointResponse) GetCheckpoints() []*Checkpoint { } type StateRequest struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` } func (m *StateRequest) Reset() { *m = StateRequest{} } func (m *StateRequest) String() string { return proto.CompactTextString(m) } func (*StateRequest) ProtoMessage() {} -func (*StateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } +func (*StateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } type ContainerState struct { Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` @@ -246,21 +276,25 @@ type ContainerState struct { func (m *ContainerState) Reset() { *m = ContainerState{} } func (m *ContainerState) String() string { return proto.CompactTextString(m) } func (*ContainerState) ProtoMessage() {} -func (*ContainerState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } +func (*ContainerState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } type Process struct { - Pid uint32 `protobuf:"varint,1,opt,name=pid" json:"pid,omitempty"` - Terminal bool `protobuf:"varint,2,opt,name=terminal" json:"terminal,omitempty"` - User *User `protobuf:"bytes,3,opt,name=user" json:"user,omitempty"` - Args []string `protobuf:"bytes,4,rep,name=args" json:"args,omitempty"` - Env []string `protobuf:"bytes,5,rep,name=env" json:"env,omitempty"` - Cwd string `protobuf:"bytes,6,opt,name=cwd" json:"cwd,omitempty"` + Pid string `protobuf:"bytes,1,opt,name=pid" json:"pid,omitempty"` + Terminal bool `protobuf:"varint,2,opt,name=terminal" json:"terminal,omitempty"` + User *User `protobuf:"bytes,3,opt,name=user" json:"user,omitempty"` + Args []string `protobuf:"bytes,4,rep,name=args" json:"args,omitempty"` + Env []string `protobuf:"bytes,5,rep,name=env" json:"env,omitempty"` + Cwd string `protobuf:"bytes,6,opt,name=cwd" json:"cwd,omitempty"` + SystemPid uint32 `protobuf:"varint,7,opt,name=systemPid" json:"systemPid,omitempty"` + Stdin string `protobuf:"bytes,8,opt,name=stdin" json:"stdin,omitempty"` + Stdout string `protobuf:"bytes,9,opt,name=stdout" json:"stdout,omitempty"` + Stderr string `protobuf:"bytes,10,opt,name=stderr" json:"stderr,omitempty"` } func (m *Process) Reset() { *m = Process{} } func (m *Process) String() string { return proto.CompactTextString(m) } func (*Process) ProtoMessage() {} -func (*Process) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } +func (*Process) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } func (m *Process) GetUser() *User { if m != nil { @@ -271,16 +305,17 @@ func (m *Process) GetUser() *User { type Container struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` - BundlePath string `protobuf:"bytes,3,opt,name=bundlePath" json:"bundlePath,omitempty"` - Processes []*Process `protobuf:"bytes,4,rep,name=processes" json:"processes,omitempty"` - Status string `protobuf:"bytes,5,opt,name=status" json:"status,omitempty"` + BundlePath string `protobuf:"bytes,2,opt,name=bundlePath" json:"bundlePath,omitempty"` + Processes []*Process `protobuf:"bytes,3,rep,name=processes" json:"processes,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status" json:"status,omitempty"` + Labels []string `protobuf:"bytes,5,rep,name=labels" json:"labels,omitempty"` + Pids []uint32 `protobuf:"varint,6,rep,name=pids" json:"pids,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 fileDescriptor0, []int{17} } +func (*Container) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } func (m *Container) GetProcesses() []*Process { if m != nil { @@ -291,15 +326,14 @@ func (m *Container) GetProcesses() []*Process { // Machine is information about machine on which containerd is run type Machine struct { - Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Cpus uint32 `protobuf:"varint,2,opt,name=cpus" json:"cpus,omitempty"` - Memory uint64 `protobuf:"varint,3,opt,name=memory" json:"memory,omitempty"` + Cpus uint32 `protobuf:"varint,1,opt,name=cpus" json:"cpus,omitempty"` + Memory uint64 `protobuf:"varint,2,opt,name=memory" json:"memory,omitempty"` } func (m *Machine) Reset() { *m = Machine{} } func (m *Machine) String() string { return proto.CompactTextString(m) } func (*Machine) ProtoMessage() {} -func (*Machine) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } +func (*Machine) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } // StateResponse is information about containerd daemon type StateResponse struct { @@ -310,7 +344,7 @@ type StateResponse struct { func (m *StateResponse) Reset() { *m = StateResponse{} } func (m *StateResponse) String() string { return proto.CompactTextString(m) } func (*StateResponse) ProtoMessage() {} -func (*StateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } +func (*StateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } func (m *StateResponse) GetContainers() []*Container { if m != nil { @@ -328,14 +362,14 @@ func (m *StateResponse) GetMachine() *Machine { type UpdateContainerRequest struct { Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Signal uint32 `protobuf:"varint,2,opt,name=signal" json:"signal,omitempty"` + Pid string `protobuf:"bytes,2,opt,name=pid" json:"pid,omitempty"` Status string `protobuf:"bytes,3,opt,name=status" json:"status,omitempty"` } func (m *UpdateContainerRequest) Reset() { *m = UpdateContainerRequest{} } func (m *UpdateContainerRequest) String() string { return proto.CompactTextString(m) } func (*UpdateContainerRequest) ProtoMessage() {} -func (*UpdateContainerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } +func (*UpdateContainerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } type UpdateContainerResponse struct { } @@ -343,53 +377,29 @@ type UpdateContainerResponse struct { func (m *UpdateContainerResponse) Reset() { *m = UpdateContainerResponse{} } func (m *UpdateContainerResponse) String() string { return proto.CompactTextString(m) } func (*UpdateContainerResponse) ProtoMessage() {} -func (*UpdateContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } +func (*UpdateContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} } type EventsRequest struct { + Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp" json:"timestamp,omitempty"` } func (m *EventsRequest) Reset() { *m = EventsRequest{} } func (m *EventsRequest) String() string { return proto.CompactTextString(m) } func (*EventsRequest) ProtoMessage() {} -func (*EventsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } +func (*EventsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} } type Event struct { - Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id" json:"id,omitempty"` - Status uint32 `protobuf:"varint,3,opt,name=status" json:"status,omitempty"` - BundlePath string `protobuf:"bytes,4,opt,name=bundlePath" json:"bundlePath,omitempty"` - Pid uint32 `protobuf:"varint,5,opt,name=pid" json:"pid,omitempty"` - Signal uint32 `protobuf:"varint,7,opt,name=signal" json:"signal,omitempty"` - Process *Process `protobuf:"bytes,8,opt,name=process" json:"process,omitempty"` - Containers []*Container `protobuf:"bytes,9,rep,name=containers" json:"containers,omitempty"` - Checkpoint *Checkpoint `protobuf:"bytes,10,opt,name=checkpoint" json:"checkpoint,omitempty"` + Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id" json:"id,omitempty"` + Status uint32 `protobuf:"varint,3,opt,name=status" json:"status,omitempty"` + Pid string `protobuf:"bytes,4,opt,name=pid" json:"pid,omitempty"` + Timestamp uint64 `protobuf:"varint,5,opt,name=timestamp" json:"timestamp,omitempty"` } func (m *Event) Reset() { *m = Event{} } func (m *Event) String() string { return proto.CompactTextString(m) } func (*Event) ProtoMessage() {} -func (*Event) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} } - -func (m *Event) GetProcess() *Process { - if m != nil { - return m.Process - } - return nil -} - -func (m *Event) GetContainers() []*Container { - if m != nil { - return m.Containers - } - return nil -} - -func (m *Event) GetCheckpoint() *Checkpoint { - if m != nil { - return m.Checkpoint - } - return nil -} +func (*Event) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} } type NetworkStats struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` @@ -406,7 +416,7 @@ type NetworkStats struct { func (m *NetworkStats) Reset() { *m = NetworkStats{} } func (m *NetworkStats) String() string { return proto.CompactTextString(m) } func (*NetworkStats) ProtoMessage() {} -func (*NetworkStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} } +func (*NetworkStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} } type CpuUsage struct { TotalUsage uint64 `protobuf:"varint,1,opt,name=total_usage" json:"total_usage,omitempty"` @@ -418,7 +428,7 @@ type CpuUsage struct { func (m *CpuUsage) Reset() { *m = CpuUsage{} } func (m *CpuUsage) String() string { return proto.CompactTextString(m) } func (*CpuUsage) ProtoMessage() {} -func (*CpuUsage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} } +func (*CpuUsage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} } type ThrottlingData struct { Periods uint64 `protobuf:"varint,1,opt,name=periods" json:"periods,omitempty"` @@ -429,7 +439,7 @@ type ThrottlingData struct { func (m *ThrottlingData) Reset() { *m = ThrottlingData{} } func (m *ThrottlingData) String() string { return proto.CompactTextString(m) } func (*ThrottlingData) ProtoMessage() {} -func (*ThrottlingData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} } +func (*ThrottlingData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{28} } type CpuStats struct { CpuUsage *CpuUsage `protobuf:"bytes,1,opt,name=cpu_usage" json:"cpu_usage,omitempty"` @@ -439,7 +449,7 @@ type CpuStats struct { func (m *CpuStats) Reset() { *m = CpuStats{} } func (m *CpuStats) String() string { return proto.CompactTextString(m) } func (*CpuStats) ProtoMessage() {} -func (*CpuStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} } +func (*CpuStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{29} } func (m *CpuStats) GetCpuUsage() *CpuUsage { if m != nil { @@ -464,7 +474,7 @@ type MemoryData struct { func (m *MemoryData) Reset() { *m = MemoryData{} } func (m *MemoryData) String() string { return proto.CompactTextString(m) } func (*MemoryData) ProtoMessage() {} -func (*MemoryData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{28} } +func (*MemoryData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{30} } type MemoryStats struct { Cache uint64 `protobuf:"varint,1,opt,name=cache" json:"cache,omitempty"` @@ -477,7 +487,7 @@ type MemoryStats struct { func (m *MemoryStats) Reset() { *m = MemoryStats{} } func (m *MemoryStats) String() string { return proto.CompactTextString(m) } func (*MemoryStats) ProtoMessage() {} -func (*MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{29} } +func (*MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} } func (m *MemoryStats) GetUsage() *MemoryData { if m != nil { @@ -517,7 +527,7 @@ type BlkioStatsEntry struct { func (m *BlkioStatsEntry) Reset() { *m = BlkioStatsEntry{} } func (m *BlkioStatsEntry) String() string { return proto.CompactTextString(m) } func (*BlkioStatsEntry) ProtoMessage() {} -func (*BlkioStatsEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{30} } +func (*BlkioStatsEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} } type BlkioStats struct { IoServiceBytesRecursive []*BlkioStatsEntry `protobuf:"bytes,1,rep,name=io_service_bytes_recursive" json:"io_service_bytes_recursive,omitempty"` @@ -533,7 +543,7 @@ type BlkioStats struct { func (m *BlkioStats) Reset() { *m = BlkioStats{} } func (m *BlkioStats) String() string { return proto.CompactTextString(m) } func (*BlkioStats) ProtoMessage() {} -func (*BlkioStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} } +func (*BlkioStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{33} } func (m *BlkioStats) GetIoServiceBytesRecursive() []*BlkioStatsEntry { if m != nil { @@ -600,7 +610,7 @@ type HugetlbStats struct { func (m *HugetlbStats) Reset() { *m = HugetlbStats{} } func (m *HugetlbStats) String() string { return proto.CompactTextString(m) } func (*HugetlbStats) ProtoMessage() {} -func (*HugetlbStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} } +func (*HugetlbStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{34} } type CgroupStats struct { CpuStats *CpuStats `protobuf:"bytes,1,opt,name=cpu_stats" json:"cpu_stats,omitempty"` @@ -612,7 +622,7 @@ type CgroupStats struct { func (m *CgroupStats) Reset() { *m = CgroupStats{} } func (m *CgroupStats) String() string { return proto.CompactTextString(m) } func (*CgroupStats) ProtoMessage() {} -func (*CgroupStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{33} } +func (*CgroupStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{35} } func (m *CgroupStats) GetCpuStats() *CpuStats { if m != nil { @@ -642,25 +652,25 @@ func (m *CgroupStats) GetHugetlbStats() map[string]*HugetlbStats { return nil } -type Stats struct { +type StatsResponse struct { NetworkStats []*NetworkStats `protobuf:"bytes,1,rep,name=network_stats" json:"network_stats,omitempty"` CgroupStats *CgroupStats `protobuf:"bytes,2,opt,name=cgroup_stats" json:"cgroup_stats,omitempty"` Timestamp uint64 `protobuf:"varint,3,opt,name=timestamp" json:"timestamp,omitempty"` } -func (m *Stats) Reset() { *m = Stats{} } -func (m *Stats) String() string { return proto.CompactTextString(m) } -func (*Stats) ProtoMessage() {} -func (*Stats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{34} } +func (m *StatsResponse) Reset() { *m = StatsResponse{} } +func (m *StatsResponse) String() string { return proto.CompactTextString(m) } +func (*StatsResponse) ProtoMessage() {} +func (*StatsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{36} } -func (m *Stats) GetNetworkStats() []*NetworkStats { +func (m *StatsResponse) GetNetworkStats() []*NetworkStats { if m != nil { return m.NetworkStats } return nil } -func (m *Stats) GetCgroupStats() *CgroupStats { +func (m *StatsResponse) GetCgroupStats() *CgroupStats { if m != nil { return m.CgroupStats } @@ -674,9 +684,11 @@ type StatsRequest struct { func (m *StatsRequest) Reset() { *m = StatsRequest{} } func (m *StatsRequest) String() string { return proto.CompactTextString(m) } func (*StatsRequest) ProtoMessage() {} -func (*StatsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{35} } +func (*StatsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{37} } func init() { + proto.RegisterType((*UpdateProcessRequest)(nil), "types.UpdateProcessRequest") + proto.RegisterType((*UpdateProcessResponse)(nil), "types.UpdateProcessResponse") proto.RegisterType((*CreateContainerRequest)(nil), "types.CreateContainerRequest") proto.RegisterType((*CreateContainerResponse)(nil), "types.CreateContainerResponse") proto.RegisterType((*SignalRequest)(nil), "types.SignalRequest") @@ -711,7 +723,7 @@ func init() { proto.RegisterType((*BlkioStats)(nil), "types.BlkioStats") proto.RegisterType((*HugetlbStats)(nil), "types.HugetlbStats") proto.RegisterType((*CgroupStats)(nil), "types.CgroupStats") - proto.RegisterType((*Stats)(nil), "types.Stats") + proto.RegisterType((*StatsResponse)(nil), "types.StatsResponse") proto.RegisterType((*StatsRequest)(nil), "types.StatsRequest") } @@ -725,13 +737,14 @@ type APIClient interface { CreateContainer(ctx context.Context, in *CreateContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error) UpdateContainer(ctx context.Context, in *UpdateContainerRequest, opts ...grpc.CallOption) (*UpdateContainerResponse, error) Signal(ctx context.Context, in *SignalRequest, opts ...grpc.CallOption) (*SignalResponse, error) + UpdateProcess(ctx context.Context, in *UpdateProcessRequest, opts ...grpc.CallOption) (*UpdateProcessResponse, error) AddProcess(ctx context.Context, in *AddProcessRequest, opts ...grpc.CallOption) (*AddProcessResponse, error) CreateCheckpoint(ctx context.Context, in *CreateCheckpointRequest, opts ...grpc.CallOption) (*CreateCheckpointResponse, error) DeleteCheckpoint(ctx context.Context, in *DeleteCheckpointRequest, opts ...grpc.CallOption) (*DeleteCheckpointResponse, error) ListCheckpoint(ctx context.Context, in *ListCheckpointRequest, opts ...grpc.CallOption) (*ListCheckpointResponse, error) State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) Events(ctx context.Context, in *EventsRequest, opts ...grpc.CallOption) (API_EventsClient, error) - GetStats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (API_GetStatsClient, error) + Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) } type aPIClient struct { @@ -769,6 +782,15 @@ func (c *aPIClient) Signal(ctx context.Context, in *SignalRequest, opts ...grpc. return out, nil } +func (c *aPIClient) UpdateProcess(ctx context.Context, in *UpdateProcessRequest, opts ...grpc.CallOption) (*UpdateProcessResponse, error) { + out := new(UpdateProcessResponse) + err := grpc.Invoke(ctx, "/types.API/UpdateProcess", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *aPIClient) AddProcess(ctx context.Context, in *AddProcessRequest, opts ...grpc.CallOption) (*AddProcessResponse, error) { out := new(AddProcessResponse) err := grpc.Invoke(ctx, "/types.API/AddProcess", in, out, c.cc, opts...) @@ -846,36 +868,13 @@ func (x *aPIEventsClient) Recv() (*Event, error) { return m, nil } -func (c *aPIClient) GetStats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (API_GetStatsClient, error) { - stream, err := grpc.NewClientStream(ctx, &_API_serviceDesc.Streams[1], c.cc, "/types.API/GetStats", opts...) +func (c *aPIClient) Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) { + out := new(StatsResponse) + err := grpc.Invoke(ctx, "/types.API/Stats", in, out, c.cc, opts...) if err != nil { return nil, err } - x := &aPIGetStatsClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type API_GetStatsClient interface { - Recv() (*Stats, error) - grpc.ClientStream -} - -type aPIGetStatsClient struct { - grpc.ClientStream -} - -func (x *aPIGetStatsClient) Recv() (*Stats, error) { - m := new(Stats) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil + return out, nil } // Server API for API service @@ -884,13 +883,14 @@ type APIServer interface { CreateContainer(context.Context, *CreateContainerRequest) (*CreateContainerResponse, error) UpdateContainer(context.Context, *UpdateContainerRequest) (*UpdateContainerResponse, error) Signal(context.Context, *SignalRequest) (*SignalResponse, error) + UpdateProcess(context.Context, *UpdateProcessRequest) (*UpdateProcessResponse, error) AddProcess(context.Context, *AddProcessRequest) (*AddProcessResponse, error) CreateCheckpoint(context.Context, *CreateCheckpointRequest) (*CreateCheckpointResponse, error) DeleteCheckpoint(context.Context, *DeleteCheckpointRequest) (*DeleteCheckpointResponse, error) ListCheckpoint(context.Context, *ListCheckpointRequest) (*ListCheckpointResponse, error) State(context.Context, *StateRequest) (*StateResponse, error) Events(*EventsRequest, API_EventsServer) error - GetStats(*StatsRequest, API_GetStatsServer) error + Stats(context.Context, *StatsRequest) (*StatsResponse, error) } func RegisterAPIServer(s *grpc.Server, srv APIServer) { @@ -933,6 +933,18 @@ func _API_Signal_Handler(srv interface{}, ctx context.Context, dec func(interfac return out, nil } +func _API_UpdateProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpdateProcessRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(APIServer).UpdateProcess(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + func _API_AddProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { in := new(AddProcessRequest) if err := dec(in); err != nil { @@ -1014,25 +1026,16 @@ func (x *aPIEventsServer) Send(m *Event) error { return x.ServerStream.SendMsg(m) } -func _API_GetStats_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(StatsRequest) - if err := stream.RecvMsg(m); err != nil { - return err +func _API_Stats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatsRequest) + if err := dec(in); err != nil { + return nil, err } - return srv.(APIServer).GetStats(m, &aPIGetStatsServer{stream}) -} - -type API_GetStatsServer interface { - Send(*Stats) error - grpc.ServerStream -} - -type aPIGetStatsServer struct { - grpc.ServerStream -} - -func (x *aPIGetStatsServer) Send(m *Stats) error { - return x.ServerStream.SendMsg(m) + out, err := srv.(APIServer).Stats(ctx, in) + if err != nil { + return nil, err + } + return out, nil } var _API_serviceDesc = grpc.ServiceDesc{ @@ -1051,6 +1054,10 @@ var _API_serviceDesc = grpc.ServiceDesc{ MethodName: "Signal", Handler: _API_Signal_Handler, }, + { + MethodName: "UpdateProcess", + Handler: _API_UpdateProcess_Handler, + }, { MethodName: "AddProcess", Handler: _API_AddProcess_Handler, @@ -1071,6 +1078,10 @@ var _API_serviceDesc = grpc.ServiceDesc{ MethodName: "State", Handler: _API_State_Handler, }, + { + MethodName: "Stats", + Handler: _API_Stats_Handler, + }, }, Streams: []grpc.StreamDesc{ { @@ -1078,105 +1089,104 @@ var _API_serviceDesc = grpc.ServiceDesc{ Handler: _API_Events_Handler, ServerStreams: true, }, - { - StreamName: "GetStats", - Handler: _API_GetStats_Handler, - ServerStreams: true, - }, }, } var fileDescriptor0 = []byte{ - // 1454 bytes of a gzipped FileDescriptorProto + // 1517 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x58, 0xd9, 0x6e, 0x1c, 0x45, - 0x17, 0xf6, 0xcc, 0xf4, 0x6c, 0x67, 0x16, 0xdb, 0xed, 0xd8, 0x19, 0xcf, 0xff, 0x87, 0x24, 0x4d, - 0x08, 0x11, 0x8a, 0xac, 0xe0, 0xb0, 0x84, 0x70, 0x01, 0xc1, 0x89, 0x12, 0x50, 0x02, 0x56, 0x62, - 0x23, 0x71, 0xc3, 0xa8, 0xdd, 0x5d, 0x8c, 0x8b, 0xe9, 0x8d, 0xee, 0x6a, 0x2f, 0xaf, 0x80, 0x78, - 0x1c, 0xc4, 0x03, 0x20, 0x71, 0xcf, 0x73, 0xf0, 0x14, 0x9c, 0x5a, 0xba, 0x7a, 0x99, 0xc5, 0x70, - 0xc1, 0xcd, 0x48, 0x55, 0x75, 0xce, 0x77, 0xce, 0xf9, 0xce, 0xd2, 0x55, 0x03, 0x5d, 0x3b, 0xa2, - 0x7b, 0x51, 0x1c, 0xb2, 0xd0, 0x6c, 0xb2, 0xcb, 0x88, 0x24, 0xd6, 0x2f, 0x35, 0xd8, 0x39, 0x88, - 0x89, 0xcd, 0xc8, 0x41, 0x18, 0x30, 0x9b, 0x06, 0x24, 0x7e, 0x4d, 0x7e, 0x4a, 0x49, 0xc2, 0x4c, - 0x80, 0x3a, 0x75, 0x47, 0xb5, 0x5b, 0xb5, 0x7b, 0x5d, 0x13, 0x17, 0x27, 0x69, 0xe0, 0x7a, 0xe4, - 0xd0, 0x66, 0xa7, 0xa3, 0xba, 0xd8, 0x1b, 0x40, 0x33, 0x61, 0x2e, 0x0d, 0x46, 0x0d, 0xb1, 0x1c, - 0x42, 0x0b, 0x97, 0x61, 0xca, 0x46, 0x46, 0x61, 0x4d, 0xe2, 0x78, 0xd4, 0x14, 0xeb, 0x75, 0x68, - 0x3b, 0x61, 0x90, 0x84, 0x1e, 0x19, 0xb5, 0x32, 0x4c, 0xe7, 0x94, 0x38, 0xb3, 0x28, 0xa4, 0x01, - 0x1b, 0xb5, 0xf9, 0x9e, 0x75, 0x17, 0xae, 0xcf, 0x79, 0x93, 0x44, 0xa8, 0x46, 0xcc, 0x1e, 0x34, - 0x22, 0xe5, 0xcf, 0xc0, 0x7a, 0x04, 0x83, 0x37, 0x74, 0x1a, 0xd8, 0xde, 0x22, 0x67, 0x95, 0x24, - 0xf7, 0x72, 0x20, 0xdc, 0x10, 0x92, 0xc2, 0xcd, 0x81, 0xb5, 0x01, 0xc3, 0x4c, 0x53, 0x02, 0x5b, - 0xbf, 0xd6, 0x60, 0xf3, 0x89, 0xeb, 0x1e, 0xc6, 0xa1, 0x43, 0x92, 0x64, 0x11, 0xe0, 0x06, 0x74, - 0x18, 0x89, 0x7d, 0xca, 0x51, 0x38, 0x6a, 0xc7, 0xdc, 0x05, 0x23, 0x4d, 0x48, 0x2c, 0x30, 0x7b, - 0xfb, 0xbd, 0x3d, 0x41, 0xe6, 0xde, 0x31, 0x6e, 0x99, 0x7d, 0x30, 0xec, 0x78, 0x9a, 0x20, 0x0b, - 0x0d, 0xe9, 0x0b, 0x09, 0xce, 0x90, 0x02, 0xb5, 0x70, 0xce, 0x5d, 0x15, 0xbe, 0xa6, 0xaf, 0x5d, - 0xa1, 0xaf, 0x53, 0xa1, 0xaf, 0x5b, 0xa5, 0x0f, 0x04, 0x55, 0x8f, 0xc0, 0x10, 0xf6, 0x10, 0x34, - 0xcd, 0x78, 0xe1, 0x8b, 0xa9, 0x0e, 0x7d, 0x07, 0x86, 0xb6, 0xeb, 0x52, 0x46, 0x43, 0x74, 0xfc, - 0x39, 0x75, 0x13, 0x74, 0xb7, 0x81, 0x14, 0xdc, 0x06, 0xb3, 0x18, 0xef, 0x22, 0x7e, 0x5f, 0xea, - 0x3c, 0xe8, 0x0c, 0x2d, 0x22, 0xe6, 0x9d, 0x52, 0x0a, 0xeb, 0x82, 0x8c, 0x4d, 0x45, 0x46, 0xae, - 0x69, 0x8d, 0x61, 0x34, 0x8f, 0xa6, 0xd8, 0x7f, 0x08, 0xd7, 0x9f, 0x12, 0x8f, 0x5c, 0x65, 0x09, - 0x59, 0x0d, 0x6c, 0x9f, 0xc8, 0xd2, 0xe3, 0x80, 0xf3, 0x4a, 0x0a, 0xf0, 0x6d, 0xd8, 0x7e, 0x49, - 0x13, 0xb6, 0x12, 0xce, 0xfa, 0x0e, 0x20, 0x17, 0xd0, 0xe0, 0xda, 0x14, 0xb9, 0xa0, 0x4c, 0x65, - 0x1a, 0x69, 0x61, 0x4e, 0x24, 0x12, 0xdd, 0x31, 0xb7, 0xa0, 0x97, 0x06, 0xf4, 0xe2, 0x4d, 0xe8, - 0xcc, 0x08, 0x4b, 0x44, 0xa1, 0x77, 0x44, 0x22, 0x4f, 0x89, 0xe7, 0x89, 0x3a, 0xef, 0x58, 0x9f, - 0xc3, 0x4e, 0xd5, 0xbe, 0x62, 0xf8, 0x2e, 0xf4, 0x72, 0xb6, 0x12, 0xb4, 0xd6, 0x58, 0x4c, 0xd7, - 0x10, 0xfa, 0x6f, 0x18, 0xb2, 0xa5, 0x1c, 0xb7, 0x6e, 0xc1, 0x50, 0xb7, 0x83, 0x38, 0x90, 0xc5, - 0x61, 0xb3, 0x34, 0x51, 0xe1, 0xcc, 0xa0, 0xad, 0xd2, 0x59, 0x4a, 0xe3, 0x7f, 0x52, 0xb8, 0x96, - 0x07, 0x5d, 0xed, 0xce, 0xf2, 0x1c, 0x55, 0x46, 0x86, 0x9c, 0x11, 0xb7, 0xa1, 0x1b, 0x49, 0x3f, - 0x89, 0xb4, 0xd3, 0xdb, 0x1f, 0x2a, 0x17, 0x32, 0xff, 0xf3, 0xd0, 0xc4, 0xd8, 0xc0, 0xfa, 0x68, - 0xbf, 0xb2, 0x9d, 0x53, 0x34, 0x56, 0xb5, 0xe5, 0x44, 0x28, 0xa4, 0x9b, 0xdc, 0x27, 0x7e, 0x18, - 0x5f, 0x0a, 0x3b, 0x86, 0xf5, 0x2d, 0x8e, 0x07, 0xc9, 0xa0, 0xa2, 0xfe, 0x0e, 0x16, 0x6a, 0xe6, - 0x73, 0xc6, 0xfc, 0x46, 0xc6, 0xbc, 0x0e, 0xe6, 0x26, 0xb4, 0x7d, 0x69, 0x4b, 0xd5, 0x72, 0xe6, - 0x9c, 0xf2, 0xc0, 0x7a, 0x0a, 0x3b, 0xc7, 0x91, 0x7b, 0xd5, 0xb0, 0xcc, 0x47, 0x4e, 0x3e, 0x82, - 0x64, 0x48, 0x82, 0x05, 0x6b, 0x17, 0xae, 0xcf, 0xa1, 0xa8, 0xe2, 0x5d, 0x87, 0xc1, 0xb3, 0x33, - 0x82, 0xd5, 0x91, 0xe5, 0xfe, 0xcf, 0x1a, 0x34, 0xc5, 0x0e, 0x8f, 0x98, 0x3b, 0xa3, 0x6c, 0x48, - 0x7b, 0xf5, 0x7c, 0x54, 0x68, 0xfc, 0x41, 0x85, 0x79, 0xa3, 0x38, 0x13, 0x9b, 0x95, 0x99, 0xd8, - 0x16, 0x6b, 0x8c, 0x5b, 0xa5, 0x45, 0x0c, 0x9f, 0xf9, 0xa4, 0x94, 0xe9, 0xeb, 0x2e, 0xa1, 0xaf, - 0x3c, 0x0d, 0x60, 0xd9, 0x34, 0xf8, 0xad, 0x06, 0xfd, 0xaf, 0x09, 0x3b, 0x0f, 0xe3, 0x19, 0x4f, - 0x52, 0x52, 0x69, 0x3f, 0xac, 0xd9, 0xf8, 0x62, 0x72, 0x72, 0xc9, 0x88, 0xcc, 0xae, 0xc1, 0xe3, - 0xc1, 0x9d, 0x43, 0x5b, 0x36, 0x9d, 0xc8, 0xb0, 0xb9, 0x09, 0xdd, 0xd7, 0x17, 0x13, 0x1c, 0x8f, - 0x61, 0x2c, 0xfb, 0x50, 0x88, 0xe1, 0x96, 0x1b, 0x87, 0x51, 0x44, 0x64, 0xa4, 0x06, 0x07, 0x3b, - 0xca, 0xc0, 0x5a, 0x99, 0x14, 0xee, 0x44, 0x0a, 0xac, 0x9d, 0x81, 0x1d, 0x69, 0xb0, 0x4e, 0x41, - 0x2c, 0x03, 0xeb, 0x8a, 0xaa, 0xf2, 0xa1, 0x73, 0x10, 0xa5, 0xc7, 0x89, 0x3d, 0x25, 0x7c, 0x12, - 0xb0, 0x90, 0xd9, 0xde, 0x24, 0xe5, 0x4b, 0xe1, 0xba, 0x61, 0x5e, 0x83, 0x7e, 0x44, 0x62, 0xac, - 0x4b, 0xb5, 0x5b, 0x47, 0xa2, 0x0c, 0xf3, 0x7f, 0xb0, 0x25, 0x96, 0x13, 0x1a, 0x4c, 0x66, 0x24, - 0x0e, 0x88, 0xe7, 0x87, 0x2e, 0x51, 0x71, 0xec, 0xc2, 0xa6, 0x3e, 0xe4, 0x8d, 0x29, 0x8e, 0x44, - 0x3c, 0xd6, 0x11, 0x0c, 0x8f, 0x4e, 0xf1, 0x5b, 0xcd, 0x3c, 0x1a, 0x4c, 0x9f, 0xda, 0xcc, 0xe6, - 0xdf, 0x00, 0xc4, 0xa7, 0xa1, 0x9b, 0x28, 0x83, 0xa8, 0xcd, 0xa4, 0x08, 0x71, 0x27, 0xd9, 0x91, - 0x24, 0x0d, 0x87, 0x7f, 0x7e, 0xc4, 0xa8, 0xaf, 0x0c, 0x5a, 0xdf, 0x8b, 0x20, 0x24, 0xf1, 0x16, - 0x74, 0x73, 0x67, 0x6b, 0x22, 0x5f, 0xeb, 0x59, 0xbe, 0xb2, 0x40, 0xf7, 0x60, 0x9d, 0x69, 0x2f, - 0x26, 0x58, 0xb5, 0xb6, 0xea, 0x8d, 0x6d, 0x25, 0x59, 0xf6, 0xd1, 0xfa, 0x0c, 0xe0, 0x95, 0x68, - 0x45, 0xe1, 0x31, 0xce, 0xc6, 0x22, 0x41, 0x48, 0xb4, 0x6f, 0x5f, 0x68, 0x76, 0xf8, 0x16, 0xc6, - 0xf4, 0x83, 0x4d, 0x3d, 0x07, 0x2b, 0x46, 0x3a, 0xf8, 0x57, 0x0d, 0x7a, 0x12, 0x41, 0x3a, 0x89, - 0x10, 0x0e, 0xb6, 0x5f, 0x06, 0x71, 0x2b, 0x43, 0x2c, 0x7f, 0x6d, 0x0a, 0x36, 0xb1, 0x0c, 0x93, - 0x73, 0x3b, 0x52, 0x56, 0x1a, 0xcb, 0xc4, 0xde, 0x85, 0xbe, 0xcc, 0x86, 0x12, 0x34, 0x96, 0x09, - 0xde, 0xe7, 0x1f, 0x6a, 0xf4, 0x44, 0xcc, 0xc2, 0xde, 0xfe, 0x8d, 0x92, 0x84, 0xf0, 0x71, 0x4f, - 0xfc, 0x3e, 0x0b, 0x58, 0x7c, 0x39, 0xbe, 0x0f, 0x90, 0xaf, 0x78, 0xdb, 0xcd, 0xc8, 0xa5, 0xaa, - 0x6c, 0x8c, 0xe4, 0xcc, 0xf6, 0x52, 0x15, 0xf9, 0xe3, 0xfa, 0xa3, 0x9a, 0xf5, 0x15, 0xac, 0x7f, - 0xe1, 0xcd, 0x68, 0x58, 0x50, 0x41, 0x29, 0xdf, 0xfe, 0x31, 0x8c, 0x55, 0xbc, 0x7c, 0x49, 0x03, - 0x5c, 0x4a, 0xba, 0xb0, 0xef, 0xc3, 0x48, 0x4d, 0x53, 0x8d, 0x27, 0xeb, 0xe5, 0xf7, 0x06, 0x40, - 0x0e, 0x66, 0x3e, 0x86, 0x31, 0x0d, 0x27, 0x58, 0x52, 0x67, 0xd4, 0x21, 0xb2, 0x05, 0x26, 0x31, - 0x71, 0xd2, 0x38, 0xa1, 0x67, 0x44, 0x8d, 0xc0, 0x1d, 0x15, 0x4b, 0xd5, 0x87, 0x0f, 0x61, 0x3b, - 0xd7, 0x75, 0x0b, 0x6a, 0xf5, 0x95, 0x6a, 0x0f, 0x61, 0x0b, 0xd5, 0x70, 0x70, 0xa5, 0x25, 0xa5, - 0xc6, 0x4a, 0xa5, 0x4f, 0x60, 0xb7, 0xe0, 0x27, 0xaf, 0xd4, 0x82, 0xaa, 0xb1, 0x52, 0xf5, 0x23, - 0xd8, 0x41, 0xd5, 0x73, 0x9b, 0xb2, 0xaa, 0x5e, 0xf3, 0x1f, 0xf8, 0xe9, 0x93, 0x78, 0x5a, 0xf2, - 0xb3, 0xb5, 0x52, 0xe9, 0x7d, 0xd8, 0x44, 0xa5, 0x8a, 0x9d, 0xf6, 0x55, 0x2a, 0x09, 0x71, 0x18, - 0x4e, 0x95, 0x82, 0x4a, 0x67, 0x95, 0x8a, 0xf5, 0x04, 0xfa, 0x2f, 0xd2, 0x29, 0x61, 0xde, 0x89, - 0xae, 0xfe, 0x7f, 0xdb, 0x40, 0x3f, 0xd7, 0xa1, 0x77, 0x30, 0x8d, 0xc3, 0x34, 0x2a, 0x75, 0xb9, - 0xac, 0xe1, 0xb9, 0x2e, 0x97, 0x32, 0xf7, 0xa0, 0x2f, 0x3f, 0xa0, 0x4a, 0x4c, 0x36, 0x97, 0x39, - 0x5f, 0xea, 0xfc, 0x12, 0x73, 0xc2, 0x7d, 0x56, 0x82, 0xe5, 0xf6, 0x2a, 0x94, 0xdf, 0xa7, 0x30, - 0x38, 0x95, 0x81, 0x28, 0x49, 0x99, 0xca, 0x3b, 0x99, 0xe5, 0xdc, 0xc1, 0xbd, 0x62, 0xc0, 0xb2, - 0x89, 0x5e, 0xc0, 0xe6, 0xdc, 0x66, 0xb9, 0x97, 0xac, 0x62, 0x2f, 0xf5, 0xf6, 0xb7, 0x14, 0x6c, - 0x51, 0x4b, 0x34, 0x58, 0x04, 0x4d, 0xe9, 0xcf, 0x7b, 0x30, 0x08, 0xe4, 0x47, 0x47, 0x33, 0xd1, - 0x28, 0x28, 0x96, 0x3e, 0x48, 0xc8, 0x86, 0x23, 0xfc, 0x5b, 0xc8, 0x46, 0x91, 0x5b, 0xcc, 0x07, - 0xaf, 0x08, 0x14, 0xf3, 0x23, 0x45, 0xff, 0x58, 0xde, 0xde, 0x16, 0x3d, 0x24, 0xf6, 0xff, 0x68, - 0x42, 0xe3, 0xc9, 0xe1, 0x97, 0xe6, 0x6b, 0x58, 0xaf, 0x3c, 0x73, 0xcc, 0x6c, 0xac, 0x2c, 0x7e, - 0x8c, 0x8d, 0xdf, 0x5a, 0x76, 0xac, 0x2e, 0x0e, 0x6b, 0x1c, 0xb3, 0x72, 0xab, 0xd0, 0x98, 0x8b, - 0xef, 0x2c, 0x1a, 0x73, 0xd9, 0x65, 0x64, 0xcd, 0xfc, 0x18, 0x5a, 0xf2, 0xb1, 0x64, 0x5e, 0x53, - 0xb2, 0xa5, 0x57, 0xd7, 0x78, 0xbb, 0xb2, 0xab, 0x15, 0x0f, 0x00, 0xf2, 0x27, 0x86, 0x39, 0x52, - 0x62, 0x73, 0xaf, 0xac, 0xf1, 0xee, 0x82, 0x13, 0x0d, 0x72, 0x0c, 0x1b, 0xd5, 0x67, 0x83, 0x59, - 0xe1, 0xa1, 0x7a, 0xc9, 0x1f, 0xdf, 0x5c, 0x7a, 0x5e, 0x84, 0xad, 0x3e, 0x1e, 0x34, 0xec, 0x92, - 0xa7, 0x88, 0x86, 0x5d, 0xfa, 0xea, 0x58, 0x33, 0xbf, 0x81, 0x61, 0xf9, 0xde, 0x6f, 0xfe, 0x5f, - 0x29, 0x2d, 0x7c, 0x8e, 0x8c, 0x6f, 0x2c, 0x39, 0xd5, 0x80, 0x1f, 0xc8, 0xd2, 0xc5, 0xbb, 0x46, - 0xc6, 0x72, 0xe1, 0x51, 0x30, 0xbe, 0x56, 0xde, 0xd4, 0x5a, 0x0f, 0xa0, 0x25, 0x6f, 0x90, 0x3a, - 0x65, 0xa5, 0x0b, 0xe5, 0xb8, 0x5f, 0xdc, 0xb5, 0xd6, 0x1e, 0xd4, 0x70, 0x4a, 0x75, 0x9e, 0x13, - 0x26, 0xeb, 0xb9, 0x68, 0x6a, 0x4e, 0x45, 0x6c, 0x72, 0x95, 0x93, 0x96, 0xf8, 0x0f, 0xe1, 0xe1, - 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa6, 0xf1, 0xd0, 0x3c, 0x50, 0x10, 0x00, 0x00, + 0x17, 0xf6, 0xec, 0x33, 0x67, 0x16, 0xdb, 0xed, 0x6d, 0x3c, 0xf9, 0xf3, 0xc7, 0x74, 0x02, 0x89, + 0x50, 0x64, 0x05, 0x87, 0x25, 0x04, 0x09, 0x08, 0x4e, 0x44, 0x40, 0x09, 0x98, 0xd8, 0x46, 0xe2, + 0x86, 0x51, 0x4f, 0x77, 0x31, 0xd3, 0xb8, 0x37, 0xba, 0xaa, 0xbd, 0x48, 0x3c, 0x01, 0xdc, 0xf3, + 0x16, 0x48, 0x5c, 0xf1, 0x00, 0x3c, 0x0e, 0x4f, 0xc1, 0xe9, 0xda, 0xa6, 0xbb, 0x67, 0x31, 0xb9, + 0xe0, 0xc6, 0x72, 0x55, 0x9d, 0xf3, 0x9d, 0xef, 0x6c, 0x55, 0x7d, 0x06, 0x5a, 0x56, 0xe4, 0xee, + 0x47, 0x71, 0xc8, 0x42, 0xa3, 0xc6, 0xae, 0x22, 0x42, 0xcd, 0x11, 0x6c, 0x9e, 0x46, 0x8e, 0xc5, + 0xc8, 0x51, 0x1c, 0xda, 0x84, 0xd2, 0x57, 0xe4, 0xa7, 0x84, 0x50, 0x66, 0x00, 0x94, 0x5d, 0xa7, + 0x5f, 0xda, 0x2b, 0xdd, 0x6b, 0x19, 0x6d, 0xa8, 0x44, 0xb8, 0x28, 0xf3, 0x05, 0x9e, 0xd8, 0x5e, + 0x48, 0xc9, 0x31, 0x73, 0xdc, 0xa0, 0x5f, 0xc1, 0xbd, 0xa6, 0xd1, 0x85, 0xda, 0x85, 0xeb, 0xb0, + 0x49, 0xbf, 0x8a, 0xcb, 0xae, 0xd1, 0x83, 0xfa, 0x84, 0xb8, 0xe3, 0x09, 0xeb, 0xd7, 0xd2, 0xb5, + 0xb9, 0x03, 0x5b, 0x05, 0x1b, 0x34, 0x0a, 0x03, 0x4a, 0xcc, 0x5f, 0x4b, 0xb0, 0x7d, 0x18, 0x13, + 0x3c, 0x39, 0x0c, 0x03, 0x66, 0xb9, 0x01, 0x89, 0xe7, 0xd9, 0xc7, 0xc5, 0x28, 0x09, 0x1c, 0x8f, + 0x1c, 0x59, 0x68, 0x63, 0x4a, 0x63, 0x42, 0xec, 0xb3, 0x28, 0x74, 0x03, 0xc6, 0x69, 0xb4, 0x52, + 0x1a, 0x94, 0xb3, 0xaa, 0xf2, 0x25, 0xd2, 0xc0, 0x65, 0x98, 0x08, 0x1a, 0x6a, 0x4d, 0xe2, 0xb8, + 0x5f, 0x57, 0x6b, 0xcf, 0x1a, 0x11, 0x8f, 0xf6, 0x1b, 0x7b, 0x95, 0x7b, 0x2d, 0xf3, 0x63, 0xd8, + 0x99, 0x21, 0x23, 0x88, 0x1a, 0xb7, 0xa1, 0x65, 0xab, 0x4d, 0x4e, 0xaa, 0x7d, 0xb0, 0xb6, 0xcf, + 0x03, 0xb8, 0xaf, 0x85, 0xcd, 0x47, 0xd0, 0x3d, 0x76, 0xc7, 0x81, 0xe5, 0x5d, 0x1b, 0xc3, 0x94, + 0x09, 0x97, 0xe4, 0xc4, 0xbb, 0xe6, 0x1a, 0xf4, 0x94, 0xa6, 0x8c, 0xcc, 0x1f, 0x25, 0x58, 0x7f, + 0xe2, 0x38, 0x4b, 0x92, 0xb2, 0x06, 0x4d, 0x46, 0x62, 0xdf, 0x4d, 0x51, 0xca, 0x3c, 0x0b, 0xbb, + 0x50, 0x4d, 0x28, 0xf2, 0xab, 0x70, 0x7e, 0x6d, 0xc9, 0xef, 0x14, 0xb7, 0x8c, 0x0e, 0x54, 0xad, + 0x78, 0x4c, 0x31, 0x30, 0x15, 0xc1, 0x85, 0x04, 0xe7, 0x18, 0x15, 0xb9, 0xb0, 0x2f, 0x1c, 0x19, + 0x12, 0xc9, 0xb2, 0x91, 0x0f, 0x67, 0xb3, 0x10, 0xce, 0x56, 0x21, 0x9c, 0x90, 0xae, 0xd1, 0xfd, + 0x2a, 0xb7, 0x85, 0x18, 0x89, 0x64, 0xd9, 0x4d, 0x17, 0x63, 0xe9, 0x76, 0xd7, 0xd8, 0x86, 0x9e, + 0xe5, 0x38, 0x2e, 0x73, 0x43, 0x24, 0xfd, 0xb9, 0xeb, 0x50, 0xa4, 0x5a, 0x41, 0xf7, 0x37, 0xc1, + 0xc8, 0xfa, 0x2a, 0x43, 0xf0, 0x42, 0xa7, 0x43, 0xe7, 0x79, 0x5e, 0x1c, 0xde, 0xcc, 0x15, 0x42, + 0x99, 0xfb, 0xbe, 0xae, 0x72, 0xa3, 0x0f, 0xcc, 0x01, 0xf4, 0x67, 0xd1, 0xa4, 0xa5, 0x87, 0xb0, + 0xf3, 0x94, 0x78, 0xe4, 0x3a, 0x4b, 0x18, 0xc4, 0xc0, 0xf2, 0x89, 0xc8, 0x61, 0x0a, 0x38, 0xab, + 0x24, 0x01, 0x6f, 0xc3, 0xd6, 0x0b, 0x97, 0xb2, 0xa5, 0x70, 0xe6, 0x77, 0x00, 0x53, 0x01, 0x0d, + 0xae, 0x4d, 0x91, 0x4b, 0x97, 0xc9, 0xc4, 0x62, 0x10, 0x99, 0x1d, 0xc9, 0x5e, 0xdb, 0x80, 0x76, + 0x12, 0xb8, 0x97, 0xc7, 0xa1, 0x7d, 0x46, 0x18, 0xe5, 0xa5, 0xce, 0x1b, 0x90, 0x4e, 0x88, 0xe7, + 0xf1, 0x4a, 0x6f, 0x9a, 0x9f, 0xc2, 0x76, 0xd1, 0xbe, 0x2c, 0xe4, 0xb7, 0xa0, 0x3d, 0x8d, 0x16, + 0x45, 0x6b, 0x95, 0x45, 0xe1, 0xea, 0x1c, 0x33, 0x8c, 0xd6, 0x3c, 0xe2, 0x7b, 0xd0, 0xd3, 0x45, + 0xcf, 0x85, 0x44, 0x29, 0x58, 0x2c, 0xa1, 0x52, 0xe2, 0xf7, 0x12, 0x34, 0x64, 0x3a, 0x55, 0x49, + 0xfd, 0x87, 0x45, 0xbb, 0x0e, 0x2d, 0x7a, 0x45, 0x19, 0xf1, 0x8f, 0x64, 0xe9, 0x76, 0x5f, 0xb7, + 0x74, 0x7f, 0x86, 0x96, 0xf6, 0xe8, 0xda, 0x9b, 0xe7, 0x0d, 0x68, 0x45, 0xc2, 0x37, 0x22, 0x0a, + 0xb8, 0x7d, 0xd0, 0x93, 0xb4, 0x95, 0xcf, 0xd3, 0x78, 0x54, 0x0b, 0x37, 0x8d, 0xa0, 0x8f, 0x9e, + 0x45, 0x69, 0xf9, 0xd7, 0x79, 0xf9, 0xdf, 0x85, 0xc6, 0x4b, 0xcb, 0x9e, 0xa0, 0xf1, 0xf4, 0xc0, + 0x8e, 0x64, 0x18, 0xf9, 0x3d, 0xea, 0x13, 0x3f, 0x8c, 0xaf, 0xb8, 0xe5, 0xaa, 0xf9, 0x2d, 0x5e, + 0x30, 0x22, 0x29, 0x32, 0x9b, 0x77, 0xb0, 0xf6, 0x15, 0x6f, 0x95, 0xcc, 0x99, 0x7b, 0xc9, 0xb8, + 0x05, 0x0d, 0x5f, 0xe0, 0xcb, 0xf6, 0x50, 0x74, 0xa5, 0x55, 0xf3, 0x09, 0x6c, 0x8b, 0xfb, 0x79, + 0xe9, 0x2d, 0x3c, 0x73, 0x83, 0x09, 0x0f, 0xf9, 0xd5, 0x6b, 0xee, 0xc2, 0xce, 0x0c, 0x84, 0x6c, + 0x06, 0x13, 0xba, 0xcf, 0xce, 0x09, 0x56, 0x9b, 0x02, 0xc5, 0x7c, 0x31, 0xd7, 0xc7, 0xff, 0x2c, + 0x3f, 0xe2, 0xd8, 0x55, 0xf3, 0x1b, 0xa8, 0x71, 0x99, 0x34, 0x00, 0x29, 0x37, 0x69, 0x52, 0x98, + 0x9f, 0x67, 0xb1, 0xab, 0xe8, 0x54, 0x55, 0x09, 0x4c, 0x21, 0x6b, 0x1c, 0xf2, 0xcf, 0x12, 0x74, + 0xbe, 0x22, 0xec, 0x22, 0x8c, 0xcf, 0xd2, 0xa0, 0xd1, 0x42, 0x87, 0x61, 0x25, 0xc6, 0x97, 0xc3, + 0xd1, 0x15, 0xc3, 0x24, 0xf2, 0xe8, 0xa6, 0xb9, 0xc6, 0x9d, 0x23, 0x4b, 0xf4, 0x55, 0x85, 0xef, + 0x21, 0xee, 0xab, 0xcb, 0x21, 0x16, 0x4a, 0x18, 0x8b, 0x5c, 0x72, 0x31, 0xdc, 0x72, 0xe2, 0x30, + 0x8a, 0x88, 0x23, 0x6c, 0xa5, 0x60, 0x27, 0x0a, 0xac, 0xae, 0xa4, 0x70, 0x27, 0x92, 0x60, 0x0d, + 0x05, 0x76, 0xa2, 0xc1, 0x9a, 0x19, 0x31, 0x05, 0xd6, 0xe2, 0xc4, 0x7d, 0x68, 0x1e, 0x46, 0xc9, + 0x29, 0xb5, 0xc6, 0x24, 0x6d, 0x76, 0x16, 0x32, 0xcb, 0x1b, 0x26, 0xe9, 0x52, 0x04, 0xcb, 0xd8, + 0x84, 0x4e, 0x44, 0x62, 0xac, 0x13, 0xb9, 0x5b, 0xc6, 0xbc, 0x57, 0x8d, 0x1b, 0xb0, 0xc1, 0x97, + 0x43, 0x37, 0x18, 0x9e, 0x91, 0x38, 0x20, 0x9e, 0x1f, 0x3a, 0x44, 0xfa, 0xb1, 0x0b, 0xeb, 0xfa, + 0x30, 0x6d, 0x37, 0x7e, 0xc4, 0xfd, 0x31, 0x4f, 0xa0, 0x77, 0x32, 0xc1, 0x2f, 0x02, 0xe6, 0xb9, + 0xc1, 0xf8, 0xa9, 0xc5, 0x2c, 0x63, 0x15, 0x1a, 0x88, 0xef, 0x86, 0x0e, 0x95, 0x06, 0x51, 0x9b, + 0x09, 0x11, 0xe2, 0x0c, 0xd5, 0x91, 0x08, 0x1a, 0x5e, 0xe9, 0xd3, 0xa3, 0x34, 0x05, 0xc2, 0xa0, + 0xf9, 0x3d, 0x77, 0x42, 0x04, 0xde, 0xc4, 0xc7, 0x53, 0x93, 0x15, 0x8f, 0xe7, 0xaa, 0x2a, 0x52, + 0xe5, 0xe8, 0x3e, 0xac, 0x32, 0xcd, 0x62, 0x88, 0x85, 0x64, 0xc9, 0x5a, 0xdd, 0x92, 0x92, 0x79, + 0x8e, 0xe6, 0x27, 0x00, 0x2f, 0x79, 0x6b, 0x70, 0xc6, 0xd8, 0xee, 0xd9, 0x00, 0x61, 0xa0, 0x7d, + 0xeb, 0x52, 0x47, 0x27, 0xdd, 0x42, 0x9f, 0x7e, 0xb0, 0x5c, 0xcf, 0x96, 0xdf, 0x0a, 0x55, 0xf3, + 0xef, 0x12, 0xb4, 0x05, 0x82, 0x20, 0x89, 0x10, 0x36, 0xb6, 0x83, 0x82, 0xd8, 0x53, 0x88, 0xf9, + 0x07, 0x25, 0x63, 0x13, 0xdf, 0x1d, 0x7a, 0x61, 0x45, 0xd2, 0x4a, 0x65, 0x91, 0xd8, 0x5d, 0xe8, + 0x88, 0x6c, 0x48, 0xc1, 0xea, 0x22, 0xc1, 0xfb, 0xe9, 0x95, 0x85, 0x4c, 0xf8, 0x15, 0xd1, 0x3e, + 0xb8, 0x99, 0x93, 0xe0, 0x1c, 0xf7, 0xf9, 0xdf, 0x67, 0x01, 0x8b, 0xaf, 0x06, 0xf7, 0x01, 0xa6, + 0xab, 0xb4, 0x17, 0xce, 0xc8, 0x95, 0xac, 0x6c, 0xf4, 0xe4, 0xdc, 0xf2, 0x12, 0xe9, 0xf9, 0xe3, + 0xf2, 0xa3, 0x92, 0xf9, 0x25, 0xac, 0x7e, 0xe6, 0x9d, 0xb9, 0x61, 0x46, 0x05, 0xa5, 0x7c, 0xeb, + 0xc7, 0x30, 0x96, 0xfe, 0xa6, 0x4b, 0x37, 0xc0, 0xa5, 0x08, 0x17, 0x36, 0x5e, 0x18, 0x4d, 0xbf, + 0xaa, 0x04, 0x9e, 0xa8, 0x97, 0xbf, 0x2a, 0x00, 0x53, 0x30, 0xe3, 0x31, 0x0c, 0xdc, 0x70, 0x88, + 0x25, 0x75, 0xee, 0xda, 0x44, 0xb4, 0xc0, 0x30, 0x26, 0x76, 0x12, 0x53, 0xf7, 0x9c, 0xc8, 0x2b, + 0x69, 0x5b, 0xfa, 0x52, 0xe4, 0xf0, 0x1e, 0x6c, 0x4d, 0x75, 0x9d, 0x8c, 0x5a, 0x79, 0xa9, 0xda, + 0x43, 0xd8, 0x40, 0x35, 0xbc, 0x4b, 0x92, 0x9c, 0x52, 0x65, 0xa9, 0xd2, 0x87, 0xb0, 0x9b, 0xe1, + 0x99, 0x56, 0x6a, 0x46, 0xb5, 0xba, 0x54, 0xf5, 0x7d, 0xd8, 0x46, 0xd5, 0x0b, 0xcb, 0x65, 0x45, + 0xbd, 0xda, 0xbf, 0xe0, 0xe9, 0x93, 0x78, 0x9c, 0xe3, 0x59, 0x5f, 0xaa, 0xf4, 0x0e, 0xac, 0xa3, + 0x52, 0xc1, 0x4e, 0xe3, 0x3a, 0x15, 0x4a, 0x6c, 0x86, 0xb7, 0x4a, 0x46, 0xa5, 0xb9, 0x4c, 0x05, + 0x6f, 0xfc, 0xce, 0xf3, 0x64, 0x4c, 0x98, 0x37, 0xd2, 0xd5, 0xff, 0xba, 0x0d, 0xf4, 0x4b, 0x19, + 0xda, 0x87, 0xe3, 0x38, 0x4c, 0xa2, 0x5c, 0x97, 0x8b, 0x1a, 0x9e, 0xe9, 0x72, 0x21, 0x73, 0x0f, + 0x3a, 0xe2, 0x41, 0x93, 0x62, 0xa2, 0xb9, 0x8c, 0xd9, 0x52, 0x4f, 0xbf, 0x53, 0x46, 0x29, 0x67, + 0x29, 0x98, 0x6f, 0xaf, 0x4c, 0xf9, 0x7d, 0x04, 0xdd, 0x89, 0x70, 0x44, 0x4a, 0x8a, 0x54, 0xde, + 0x51, 0x96, 0xa7, 0x04, 0xf7, 0xb3, 0x0e, 0x8b, 0x26, 0x7a, 0x0e, 0xeb, 0x33, 0x9b, 0xf9, 0x5e, + 0x32, 0xb3, 0xbd, 0xd4, 0x3e, 0xd8, 0x90, 0xb0, 0x59, 0x2d, 0xde, 0x60, 0x97, 0xe2, 0x65, 0xd6, + 0x1f, 0xaf, 0xc6, 0xdb, 0xd0, 0x0d, 0xc4, 0xe3, 0xa3, 0x23, 0x52, 0xc9, 0x00, 0xe4, 0x1e, 0x26, + 0x8c, 0x8a, 0xcd, 0x79, 0xce, 0x8d, 0x4a, 0x36, 0xc6, 0xb9, 0x67, 0x4e, 0xa4, 0x41, 0x7e, 0xa8, + 0xcd, 0x1b, 0x11, 0x0e, 0x7e, 0xab, 0x43, 0xe5, 0xc9, 0xd1, 0x17, 0xc6, 0x2b, 0x58, 0x2d, 0x0c, + 0x36, 0x86, 0xba, 0x5e, 0xe6, 0x4f, 0x5f, 0x83, 0xff, 0x2f, 0x3a, 0x96, 0x6f, 0xfa, 0x4a, 0x8a, + 0x59, 0x78, 0xf0, 0x35, 0xe6, 0xfc, 0x6f, 0x09, 0x8d, 0xb9, 0xe8, 0x3b, 0x61, 0xc5, 0xf8, 0x00, + 0xea, 0x62, 0x0c, 0x32, 0x36, 0xa5, 0x6c, 0x6e, 0x9e, 0x1a, 0x6c, 0x15, 0x76, 0xb5, 0xe2, 0x0b, + 0xe8, 0xe6, 0x06, 0x4c, 0xe3, 0x46, 0xce, 0x56, 0x7e, 0x8a, 0x1a, 0xfc, 0x6f, 0xfe, 0xa1, 0x46, + 0x3b, 0x04, 0x98, 0x8e, 0x23, 0x46, 0x5f, 0x4a, 0xcf, 0x4c, 0x63, 0x83, 0xdd, 0x39, 0x27, 0x1a, + 0xe4, 0x14, 0xd6, 0x8a, 0xf3, 0x86, 0x51, 0x88, 0x6a, 0x71, 0x3a, 0x18, 0xdc, 0x5a, 0x78, 0x9e, + 0x85, 0x2d, 0x4e, 0x1d, 0x1a, 0x76, 0xc1, 0x0c, 0xa3, 0x61, 0x17, 0x8e, 0x2b, 0x2b, 0xc6, 0xd7, + 0xd0, 0xcb, 0x0f, 0x0c, 0x86, 0x0a, 0xd2, 0xdc, 0x39, 0x66, 0x70, 0x73, 0xc1, 0xa9, 0x06, 0x7c, + 0x17, 0x6a, 0x62, 0x34, 0x50, 0x15, 0x9f, 0x9d, 0x26, 0x06, 0x9b, 0xf9, 0x4d, 0xad, 0xf5, 0x00, + 0xea, 0xe2, 0x53, 0x51, 0x17, 0x40, 0xee, 0xcb, 0x71, 0xd0, 0xc9, 0xee, 0x9a, 0x2b, 0x0f, 0x4a, + 0xca, 0x0e, 0xcd, 0xd9, 0xa1, 0xf3, 0xec, 0x64, 0x92, 0x33, 0xaa, 0xf3, 0x9f, 0x40, 0x1e, 0xfe, + 0x13, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xed, 0x36, 0x83, 0x0f, 0x11, 0x00, 0x00, } diff --git a/api/grpc/types/api.proto b/api/grpc/types/api.proto index 08963cc..4b6b4a0 100644 --- a/api/grpc/types/api.proto +++ b/api/grpc/types/api.proto @@ -6,32 +6,44 @@ service API { 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 GetStats(StatsRequest) returns (stream Stats) {} + rpc Stats(StatsRequest) returns (StatsResponse) {} +} + +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 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) + 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; } message CreateContainerResponse { - uint32 pid = 1; // PID of the containers main process + Container container = 1; } message SignalRequest { string id = 1; // ID of container - uint32 pid = 2; // PID of process inside 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" } @@ -45,11 +57,11 @@ message AddProcessRequest { 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 stdin = 7; // path to the file where stdin will be read (optional) - string stdout = 8; // path to file where stdout will be written (optional) - string stderr = 9; // path to file where stderr will be written (optional) - string console = 10; // path to the console for a container (optional) -}; + 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) +} message User { uint32 uid = 1; // UID of user @@ -58,7 +70,6 @@ message User { } message AddProcessResponse { - uint32 pid = 1; // PID of process is returned in case of success } message CreateCheckpointRequest { @@ -94,6 +105,7 @@ message ListCheckpointResponse { } message StateRequest { + string id = 1; // container id for a single container } message ContainerState { @@ -101,27 +113,31 @@ message ContainerState { } message Process { - uint32 pid = 1; + 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) } message Container { string id = 1; // ID of container - string name = 2; // Name of container (???) - string bundlePath = 3; // Path to OCI bundle - repeated Process processes = 4; // List of processes which run in container - string status = 5; // Container status ("running", "paused", etc.) + 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; } // Machine is information about machine on which containerd is run message Machine { - string id = 1; // ID of machine - uint32 cpus = 2; // number of cpus - uint64 memory = 3; // amount of memory + uint32 cpus = 1; // number of cpus + uint64 memory = 2; // amount of memory } // StateResponse is information about containerd daemon @@ -132,7 +148,7 @@ message StateResponse { message UpdateContainerRequest { string id = 1; // ID of container - uint32 signal = 2; // Signal + string pid = 2; string status = 3; // Status to whcih containerd will try to change } @@ -140,18 +156,15 @@ message UpdateContainerResponse { } message EventsRequest { + uint64 timestamp = 1; } message Event { string type = 1; string id = 2; uint32 status = 3; - string bundlePath = 4; - uint32 pid = 5; - uint32 signal = 7; - Process process = 8; - repeated Container containers = 9; - Checkpoint checkpoint = 10; + string pid = 4; + uint64 timestamp = 5; } message NetworkStats { @@ -229,7 +242,7 @@ message CgroupStats { map hugetlb_stats = 4; // the map is in the format "size of hugepage: stats of the hugepage" } -message Stats { +message StatsResponse { repeated NetworkStats network_stats = 1; CgroupStats cgroup_stats = 2; uint64 timestamp = 3; diff --git a/containerd-shim/example/config.json b/containerd-shim/example/config.json new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/exit b/containerd-shim/example/init/exit new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/pid b/containerd-shim/example/init/pid new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/process.json b/containerd-shim/example/init/process.json new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/resize b/containerd-shim/example/init/resize new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/stderr b/containerd-shim/example/init/stderr new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/stdin b/containerd-shim/example/init/stdin new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/init/stdout b/containerd-shim/example/init/stdout new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/exit b/containerd-shim/example/logger/exit new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/pid b/containerd-shim/example/logger/pid new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/process.json b/containerd-shim/example/logger/process.json new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/resize b/containerd-shim/example/logger/resize new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/stderr b/containerd-shim/example/logger/stderr new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/stdin b/containerd-shim/example/logger/stdin new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/example/logger/stdout b/containerd-shim/example/logger/stdout new file mode 100644 index 0000000..e69de29 diff --git a/containerd-shim/main.go b/containerd-shim/main.go new file mode 100644 index 0000000..a7dcbe3 --- /dev/null +++ b/containerd-shim/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/docker/containerd/util" + "github.com/docker/docker/pkg/term" +) + +func setupLogger() { + f, err := os.OpenFile("/tmp/shim.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + panic(err) + } + logrus.SetOutput(f) +} + +// containerd-shim is a small shim that sits in front of a runc implementation +// that allows it to be repartented to init and handle reattach from the caller. +// +// the cwd of the shim should be the bundle for the container. Arg1 should be the path +// to the state directory where the shim can locate fifos and other information. +func main() { + flag.Parse() + // start handling signals as soon as possible so that things are properly reaped + // or if runc exits before we hit the handler + signals := make(chan os.Signal, 2048) + signal.Notify(signals) + // set the shim as the subreaper for all orphaned processes created by the container + if err := util.SetSubreaper(1); err != nil { + logrus.WithField("error", err).Fatal("shim: set as subreaper") + } + // open the exit pipe + f, err := os.OpenFile("exit", syscall.O_WRONLY, 0) + if err != nil { + logrus.WithField("error", err).Fatal("shim: open exit pipe") + } + defer f.Close() + control, err := os.OpenFile("control", syscall.O_RDWR, 0) + if err != nil { + logrus.WithField("error", err).Fatal("shim: open control pipe") + } + defer control.Close() + p, err := newProcess(flag.Arg(0), flag.Arg(1)) + if err != nil { + logrus.WithField("error", err).Fatal("shim: create new process") + } + if err := p.start(); err != nil { + p.delete() + logrus.WithField("error", err).Fatal("shim: start process") + } + go func() { + for { + var msg, w, h int + if _, err := fmt.Fscanf(control, "%d %d %d\n", &msg, &w, &h); err != nil { + logrus.WithField("error", err).Error("shim: reading from control") + } + logrus.Info("got control message") + switch msg { + case 0: + // close stdin + p.shimIO.Stdin.Close() + case 1: + if p.console == nil { + continue + } + ws := term.Winsize{ + Width: uint16(w), + Height: uint16(h), + } + term.SetWinsize(p.console.Fd(), &ws) + } + } + }() + var exitShim bool + for s := range signals { + logrus.WithField("signal", s).Debug("shim: received signal") + switch s { + case syscall.SIGCHLD: + exits, err := util.Reap() + if err != nil { + logrus.WithField("error", err).Error("shim: reaping child processes") + } + for _, e := range exits { + // check to see if runc is one of the processes that has exited + if e.Pid == p.pid() { + exitShim = true + logrus.WithFields(logrus.Fields{ + "pid": e.Pid, + "status": e.Status, + }).Info("shim: runc exited") + if err := writeInt("exitStatus", e.Status); err != nil { + logrus.WithFields(logrus.Fields{ + "error": err, + "status": e.Status, + }).Error("shim: write exit status") + } + } + } + } + // runc has exited so the shim can also exit + if exitShim { + if err := p.Close(); err != nil { + logrus.WithField("error", err).Error("shim: close stdio") + } + if err := p.delete(); err != nil { + logrus.WithField("error", err).Error("shim: delete runc state") + } + return + } + } +} + +func writeInt(path string, i int) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + _, err = fmt.Fprintf(f, "%d", i) + return err +} diff --git a/containerd-shim/process.go b/containerd-shim/process.go new file mode 100644 index 0000000..c7a3e37 --- /dev/null +++ b/containerd-shim/process.go @@ -0,0 +1,273 @@ +package main + +import ( + "encoding/json" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "syscall" + + "github.com/docker/containerd/runtime" + "github.com/opencontainers/runc/libcontainer" +) + +type process struct { + id string + bundle string + stdio *stdio + exec bool + containerPid int + checkpoint *runtime.Checkpoint + shimIO *IO + console libcontainer.Console + consolePath string + state *runtime.ProcessState +} + +func newProcess(id, bundle string) (*process, error) { + p := &process{ + id: id, + bundle: bundle, + } + s, err := loadProcess() + if err != nil { + return nil, err + } + p.state = s + if s.Checkpoint != "" { + cpt, err := loadCheckpoint(bundle, s.Checkpoint) + if err != nil { + return nil, err + } + p.checkpoint = cpt + } + if err := p.openIO(); err != nil { + return nil, err + } + return p, nil +} + +func loadProcess() (*runtime.ProcessState, error) { + f, err := os.Open("process.json") + if err != nil { + return nil, err + } + defer f.Close() + var s runtime.ProcessState + if err := json.NewDecoder(f).Decode(&s); err != nil { + return nil, err + } + return &s, nil +} + +func loadCheckpoint(bundle, name string) (*runtime.Checkpoint, error) { + f, err := os.Open(filepath.Join(bundle, "checkpoints", name, "config.json")) + if err != nil { + return nil, err + } + defer f.Close() + var cpt runtime.Checkpoint + if err := json.NewDecoder(f).Decode(&cpt); err != nil { + return nil, err + } + return &cpt, nil +} + +func (p *process) start() error { + cwd, err := os.Getwd() + if err != nil { + return err + } + args := []string{} + if p.state.Exec { + args = append(args, "exec", + "--process", filepath.Join(cwd, "process.json"), + "--console", p.consolePath, + ) + } else if p.checkpoint != nil { + args = append(args, "restore", + "--image-path", filepath.Join(p.bundle, "checkpoints", p.checkpoint.Name), + ) + add := func(flags ...string) { + args = append(args, flags...) + } + if p.checkpoint.Shell { + add("--shell-job") + } + if p.checkpoint.Tcp { + add("--tcp-established") + } + if p.checkpoint.UnixSockets { + add("--ext-unix-sk") + } + } else { + args = append(args, "start", + "--bundle", p.bundle, + "--console", p.consolePath, + ) + } + args = append(args, + "-d", + "--pid-file", filepath.Join(cwd, "pid"), + p.id, + ) + cmd := exec.Command("runc", args...) + cmd.Dir = p.bundle + cmd.Stdin = p.stdio.stdin + cmd.Stdout = p.stdio.stdout + cmd.Stderr = p.stdio.stderr + // set the parent death signal to SIGKILL so that if the shim dies the container + // process also dies + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + } + if err := cmd.Run(); err != nil { + return err + } + data, err := ioutil.ReadFile("pid") + if err != nil { + return err + } + pid, err := strconv.Atoi(string(data)) + if err != nil { + return err + } + p.containerPid = pid + return nil + +} + +func (p *process) pid() int { + return p.containerPid +} + +func (p *process) delete() error { + if !p.state.Exec { + return exec.Command("runc", "delete", p.id).Run() + } + return nil +} + +// openIO opens the pre-created fifo's for use with the container +// in RDWR so that they remain open if the other side stops listening +func (p *process) openIO() error { + p.stdio = &stdio{} + var ( + uid = p.state.RootUID + gid = p.state.RootGID + ) + if p.state.Terminal { + console, err := libcontainer.NewConsole(uid, gid) + if err != nil { + return err + } + p.console = console + p.consolePath = console.Path() + stdin, err := os.OpenFile(p.state.Stdin, syscall.O_RDWR, 0) + if err != nil { + return err + } + go io.Copy(console, stdin) + stdout, err := os.OpenFile(p.state.Stdout, syscall.O_RDWR, 0) + if err != nil { + return err + } + go func() { + io.Copy(stdout, console) + console.Close() + }() + return nil + } + i, err := p.initializeIO(uid) + if err != nil { + return err + } + p.shimIO = i + // non-tty + for name, dest := range map[string]func(f *os.File){ + p.state.Stdin: func(f *os.File) { + go io.Copy(i.Stdin, f) + }, + p.state.Stdout: func(f *os.File) { + go io.Copy(f, i.Stdout) + }, + p.state.Stderr: func(f *os.File) { + go io.Copy(f, i.Stderr) + }, + } { + f, err := os.OpenFile(name, syscall.O_RDWR, 0) + if err != nil { + return err + } + dest(f) + } + return nil +} + +type IO struct { + Stdin io.WriteCloser + Stdout io.ReadCloser + Stderr io.ReadCloser +} + +func (p *process) initializeIO(rootuid int) (i *IO, err error) { + var fds []uintptr + i = &IO{} + // cleanup in case of an error + defer func() { + if err != nil { + for _, fd := range fds { + syscall.Close(int(fd)) + } + } + }() + // STDIN + r, w, err := os.Pipe() + if err != nil { + return nil, err + } + fds = append(fds, r.Fd(), w.Fd()) + p.stdio.stdin, i.Stdin = r, w + // STDOUT + if r, w, err = os.Pipe(); err != nil { + return nil, err + } + fds = append(fds, r.Fd(), w.Fd()) + p.stdio.stdout, i.Stdout = w, r + // STDERR + if r, w, err = os.Pipe(); err != nil { + return nil, err + } + fds = append(fds, r.Fd(), w.Fd()) + p.stdio.stderr, i.Stderr = w, r + // change ownership of the pipes incase we are in a user namespace + for _, fd := range fds { + if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil { + return nil, err + } + } + return i, nil +} +func (p *process) Close() error { + return p.stdio.Close() +} + +type stdio struct { + stdin *os.File + stdout *os.File + stderr *os.File +} + +func (s *stdio) Close() error { + err := s.stdin.Close() + if oerr := s.stdout.Close(); err == nil { + err = oerr + } + if oerr := s.stderr.Close(); err == nil { + err = oerr + } + return err +} diff --git a/containerd/main.go b/containerd/main.go index d1abce8..a5a5558 100644 --- a/containerd/main.go +++ b/containerd/main.go @@ -4,6 +4,7 @@ import ( "log" "net" "os" + "os/signal" "runtime" "sync" "syscall" @@ -29,11 +30,6 @@ const ( ) var daemonFlags = []cli.Flag{ - cli.StringFlag{ - Name: "id", - Value: getDefaultID(), - Usage: "unique containerd id to identify the instance", - }, cli.BoolFlag{ Name: "debug", Usage: "enable debug output in the logs", @@ -43,14 +39,9 @@ var daemonFlags = []cli.Flag{ Value: "/run/containerd", Usage: "runtime state directory", }, - cli.IntFlag{ - Name: "c,concurrency", - Value: 10, - Usage: "set the concurrency level for tasks", - }, cli.DurationFlag{ Name: "metrics-interval", - Value: 60 * time.Second, + Value: 120 * time.Second, Usage: "interval for flushing metrics to the store", }, cli.StringFlag{ @@ -88,10 +79,9 @@ func main() { } app.Action = func(context *cli.Context) { if err := daemon( - context.String("id"), context.String("listen"), context.String("state-dir"), - context.Int("concurrency"), + 10, context.Bool("oom-notify"), ); err != nil { logrus.Fatal(err) @@ -111,7 +101,7 @@ func checkLimits() error { logrus.WithFields(logrus.Fields{ "current": l.Cur, "max": l.Max, - }).Warn("low RLIMIT_NOFILE changing to max") + }).Warn("containerd: low RLIMIT_NOFILE changing to max") l.Cur = l.Max return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) } @@ -130,7 +120,6 @@ func debugMetrics(interval time.Duration, graphiteAddr string) error { if err != nil { return err } - logrus.Debugf("Sending metrics to Graphite server on %s", graphiteAddr) go graphite.Graphite(metrics.DefaultRegistry, 10e9, "metrics", addr) } else { l := log.New(os.Stdout, "[containerd] ", log.LstdFlags) @@ -154,13 +143,13 @@ func processMetrics() { // collect the number of open fds fds, err := util.GetOpenFds(os.Getpid()) if err != nil { - logrus.WithField("error", err).Error("get open fd count") + logrus.WithField("error", err).Error("containerd: get open fd count") } fg.Update(int64(fds)) // get the memory used m := sigar.ProcMem{} if err := m.Get(os.Getpid()); err != nil { - logrus.WithField("error", err).Error("get pid memory information") + logrus.WithField("error", err).Error("containerd: get pid memory information") } memg.Update(int64(m.Size)) } @@ -172,9 +161,11 @@ func processMetrics() { }() } -func daemon(id, address, stateDir string, concurrency int, oom bool) error { - tasks := make(chan *supervisor.StartTask, concurrency*100) - sv, err := supervisor.New(id, stateDir, tasks, oom) +func daemon(address, stateDir string, concurrency int, oom bool) error { + // setup a standard reaper so that we don't leave any zombies if we are still alive + // this is just good practice because we are spawning new processes + go reapProcesses() + sv, err := supervisor.New(stateDir, oom) if err != nil { return err } @@ -184,17 +175,6 @@ func daemon(id, address, stateDir string, concurrency int, oom bool) error { w := supervisor.NewWorker(sv, wg) go w.Start() } - // only set containerd as the subreaper if it is not an init process - if pid := os.Getpid(); pid != 1 { - logrus.WithFields(logrus.Fields{ - "pid": pid, - }).Debug("containerd is not init, set as subreaper") - if err := setSubReaper(); err != nil { - return err - } - } - // start the signal handler in the background. - go startSignalHandler(sv) if err := sv.Start(); err != nil { return err } @@ -207,10 +187,23 @@ func daemon(id, address, stateDir string, concurrency int, oom bool) error { } s := grpc.NewServer() types.RegisterAPIServer(s, server.NewServer(sv)) - logrus.Debugf("GRPC API listen on %s", address) + logrus.Debugf("containerd: grpc api on %s", address) return s.Serve(l) } +func reapProcesses() { + s := make(chan os.Signal, 2048) + signal.Notify(s, syscall.SIGCHLD) + if err := util.SetSubreaper(1); err != nil { + logrus.WithField("error", err).Error("containerd: set subpreaper") + } + for range s { + if _, err := util.Reap(); err != nil { + logrus.WithField("error", err).Error("containerd: reap child processes") + } + } +} + // getDefaultID returns the hostname for the instance host func getDefaultID() string { hostname, err := os.Hostname() diff --git a/containerd/reap_linux.go b/containerd/reap_linux.go deleted file mode 100644 index 0459c46..0000000 --- a/containerd/reap_linux.go +++ /dev/null @@ -1,67 +0,0 @@ -// +build linux - -package main - -import ( - "os" - "os/signal" - "syscall" - - "github.com/Sirupsen/logrus" - "github.com/docker/containerd/supervisor" - "github.com/docker/containerd/util" - "github.com/opencontainers/runc/libcontainer/utils" -) - -const signalBufferSize = 2048 - -func startSignalHandler(supervisor *supervisor.Supervisor) { - logrus.WithFields(logrus.Fields{ - "bufferSize": signalBufferSize, - }).Debug("containerd: starting signal handler") - signals := make(chan os.Signal, signalBufferSize) - signal.Notify(signals) - for s := range signals { - switch s { - case syscall.SIGTERM, syscall.SIGINT: - supervisor.Stop(signals) - case syscall.SIGCHLD: - exits, err := reap() - if err != nil { - logrus.WithField("error", err).Error("containerd: reaping child processes") - } - for _, e := range exits { - supervisor.SendEvent(e) - } - } - } - supervisor.Close() - os.Exit(0) -} - -func reap() (exits []*supervisor.Event, err error) { - var ( - ws syscall.WaitStatus - rus syscall.Rusage - ) - for { - pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus) - if err != nil { - if err == syscall.ECHILD { - return exits, nil - } - return exits, err - } - if pid <= 0 { - return exits, nil - } - e := supervisor.NewEvent(supervisor.ExitEventType) - e.Pid = pid - e.Status = utils.ExitStatus(ws) - exits = append(exits, e) - } -} - -func setSubReaper() error { - return util.SetSubreaper(1) -} diff --git a/ctr/checkpoint.go b/ctr/checkpoint.go index 43049ee..5332a84 100644 --- a/ctr/checkpoint.go +++ b/ctr/checkpoint.go @@ -16,6 +16,7 @@ var checkpointCommand = cli.Command{ Subcommands: []cli.Command{ listCheckpointCommand, createCheckpointCommand, + deleteCheckpointCommand, }, Action: listCheckpoints, } @@ -86,7 +87,11 @@ var createCheckpointCommand = cli.Command{ if _, err := c.CreateCheckpoint(netcontext.Background(), &types.CreateCheckpointRequest{ Id: containerID, Checkpoint: &types.Checkpoint{ - Name: name, + Name: name, + Exit: context.Bool("exit"), + Tcp: context.Bool("tcp"), + Shell: context.Bool("shell"), + UnixSockets: context.Bool("unix-sockets"), }, }); err != nil { fatal(err.Error(), 1) diff --git a/ctr/container.go b/ctr/container.go index af793c4..4e2bf98 100644 --- a/ctr/container.go +++ b/ctr/container.go @@ -5,9 +5,12 @@ import ( "fmt" "io" "io/ioutil" + "log" "net" "os" + "os/signal" "path/filepath" + "strings" "syscall" "text/tabwriter" "time" @@ -15,14 +18,16 @@ import ( "github.com/codegangsta/cli" "github.com/docker/containerd/api/grpc/types" "github.com/docker/docker/pkg/term" - "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/specs" netcontext "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" ) // TODO: parse flags and pass opts func getClient(ctx *cli.Context) types.APIClient { + // 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) { @@ -49,6 +54,25 @@ var containersCommand = cli.Command{ 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", @@ -57,14 +81,20 @@ var listCommand = cli.Command{ func listContainers(context *cli.Context) { c := getClient(context) - resp, err := c.State(netcontext.Background(), &types.StateRequest{}) + 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\tPID1\n") + fmt.Fprint(w, "ID\tPATH\tSTATUS\tPROCESSES\n") for _, c := range resp.Containers { - fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", c.Id, c.BundlePath, c.Status, c.Processes[0].Pid) + 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) @@ -84,6 +114,11 @@ var startCommand = cli.Command{ 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", + }, }, Action: func(context *cli.Context) { var ( @@ -100,35 +135,47 @@ var startCommand = cli.Command{ if err != nil { fatal(fmt.Sprintf("cannot get the absolute path of the bundle: %v", err), 1) } - c := getClient(context) - events, err := c.Events(netcontext.Background(), &types.EventsRequest{}) + s, err := createStdio() if err != nil { fatal(err.Error(), 1) } - r := &types.CreateContainerRequest{ - Id: id, - BundlePath: bpath, - Checkpoint: context.String("checkpoint"), - } + var ( + tty bool + c = getClient(context) + r = &types.CreateContainerRequest{ + Id: id, + BundlePath: bpath, + Checkpoint: context.String("checkpoint"), + Stdin: s.stdin, + Stdout: s.stdout, + Stderr: s.stderr, + Labels: context.StringSlice("label"), + } + ) if context.Bool("attach") { mkterm, err := readTermSetting(bpath) if err != nil { fatal(err.Error(), 1) } + tty = mkterm if mkterm { - if err := attachTty(&r.Console); err != nil { - fatal(err.Error(), 1) - } - } else { - if err := attachStdio(&r.Stdin, &r.Stdout, &r.Stderr); err != nil { + 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) } } - resp, err := c.CreateContainer(netcontext.Background(), r) + 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") { restoreAndCloseStdin := func() { if state != nil { @@ -138,25 +185,50 @@ var startCommand = cli.Command{ } go func() { io.Copy(stdin, os.Stdin) - restoreAndCloseStdin() - }() - for { - e, err := events.Recv() - if err != nil { - restoreAndCloseStdin() + if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{ + Id: id, + Pid: "init", + CloseStdin: true, + }); err != nil { fatal(err.Error(), 1) } - if e.Id == id && e.Type == "exit" { - restoreAndCloseStdin() - os.Exit(int(e.Status)) - } + 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) + } + } + }() + } + if err := waitForExit(c, events, id, "init", restoreAndCloseStdin); err != nil { + fatal(err.Error(), 1) } - } else { - fmt.Println(resp.Pid) } }, } +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 @@ -177,69 +249,23 @@ func readTermSetting(path string) (bool, error) { return spec.Process.Terminal, nil } -func attachTty(consolePath *string) error { - console, err := libcontainer.NewConsole(os.Getuid(), os.Getgid()) +func attachStdio(s stdio) error { + stdinf, err := os.OpenFile(s.stdin, syscall.O_RDWR, 0) if err != nil { return err } - *consolePath = console.Path() - stdin = console - go func() { - io.Copy(os.Stdout, console) - console.Close() - }() - s, err := term.SetRawTerminal(os.Stdin.Fd()) + // FIXME: assign to global + stdin = stdinf + stdoutf, err := os.OpenFile(s.stdout, syscall.O_RDWR, 0) if err != nil { return err } - state = s - return nil -} - -func attachStdio(stdins, stdout, stderr *string) error { - dir, err := ioutil.TempDir("", "ctr-") + go io.Copy(os.Stdout, stdoutf) + stderrf, err := os.OpenFile(s.stderr, syscall.O_RDWR, 0) if err != nil { return err } - for _, p := range []struct { - path string - flag int - done func(f *os.File) - }{ - { - path: filepath.Join(dir, "stdin"), - flag: syscall.O_RDWR, - done: func(f *os.File) { - *stdins = filepath.Join(dir, "stdin") - stdin = f - }, - }, - { - path: filepath.Join(dir, "stdout"), - flag: syscall.O_RDWR, - done: func(f *os.File) { - *stdout = filepath.Join(dir, "stdout") - go io.Copy(os.Stdout, f) - }, - }, - { - path: filepath.Join(dir, "stderr"), - flag: syscall.O_RDWR, - done: func(f *os.File) { - *stderr = filepath.Join(dir, "stderr") - go io.Copy(os.Stderr, f) - }, - }, - } { - if err := syscall.Mkfifo(p.path, 0755); err != nil { - return fmt.Errorf("mkfifo: %s %v", p.path, err) - } - f, err := os.OpenFile(p.path, p.flag, 0) - if err != nil { - return fmt.Errorf("open: %s %v", p.path, err) - } - p.done(f) - } + go io.Copy(os.Stderr, stderrf) return nil } @@ -247,8 +273,9 @@ var killCommand = cli.Command{ Name: "kill", Usage: "send a signal to a container or its processes", Flags: []cli.Flag{ - cli.IntFlag{ + cli.StringFlag{ Name: "pid,p", + Value: "init", Usage: "pid of the process to signal within the container", }, cli.IntFlag{ @@ -265,7 +292,7 @@ var killCommand = cli.Command{ c := getClient(context) if _, err := c.Signal(netcontext.Background(), &types.SignalRequest{ Id: id, - Pid: uint32(context.Int("pid")), + Pid: context.String("pid"), Signal: uint32(context.Int("signal")), }); err != nil { fatal(err.Error(), 1) @@ -281,6 +308,10 @@ var execCommand = cli.Command{ 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", @@ -309,52 +340,76 @@ var execCommand = cli.Command{ }, Action: func(context *cli.Context) { p := &types.AddProcessRequest{ + Id: context.String("id"), + Pid: context.String("pid"), Args: context.Args(), Cwd: context.String("cwd"), Terminal: context.Bool("tty"), - Id: context.String("id"), Env: context.StringSlice("env"), User: &types.User{ Uid: uint32(context.Int("uid")), Gid: uint32(context.Int("gid")), }, } + s, err := createStdio() + if err != nil { + fatal(err.Error(), 1) + } + p.Stdin = s.stdin + p.Stdout = s.stdout + p.Stderr = s.stderr + 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 context.Bool("attach") { - if p.Terminal { - if err := attachTty(&p.Console); err != nil { - fatal(err.Error(), 1) - } - } else { - if err := attachStdio(&p.Stdin, &p.Stdout, &p.Stderr); err != nil { - fatal(err.Error(), 1) - } - } - } - r, err := c.AddProcess(netcontext.Background(), p) - if err != nil { + if _, err := c.AddProcess(netcontext.Background(), p); err != nil { fatal(err.Error(), 1) } if context.Bool("attach") { - go func() { - io.Copy(stdin, os.Stdin) + restoreAndCloseStdin := func() { if state != nil { term.RestoreTerminal(os.Stdin.Fd(), state) } stdin.Close() + } + 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() }() - for { - e, err := events.Recv() - if err != nil { - fatal(err.Error(), 1) - } - if e.Pid == r.Pid && e.Type == "exit" { - os.Exit(int(e.Status)) - } + 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) + } + } + }() + } + if err := waitForExit(c, events, context.String("id"), context.String("pid"), restoreAndCloseStdin); err != nil { + fatal(err.Error(), 1) } } }, @@ -368,16 +423,56 @@ var statsCommand = cli.Command{ Id: context.Args().First(), } c := getClient(context) - stream, err := c.GetStats(netcontext.Background(), req) + stats, err := c.Stats(netcontext.Background(), req) if err != nil { fatal(err.Error(), 1) } - for { - stats, err := stream.Recv() - if err != nil { - fatal(err.Error(), 1) - } - fmt.Println(stats) + data, err := json.Marshal(stats) + if err != nil { + fatal(err.Error(), 1) } + fmt.Print(string(data)) }, } + +func waitForExit(c types.APIClient, events types.API_EventsClient, id, pid string, closer func()) error { + for { + e, err := events.Recv() + if err != nil { + time.Sleep(1 * time.Second) + events, _ = c.Events(netcontext.Background(), &types.EventsRequest{}) + continue + } + if e.Id == id && e.Type == "exit" && e.Pid == pid { + closer() + os.Exit(int(e.Status)) + } + } + return nil +} + +type stdio struct { + stdin string + stdout string + stderr string +} + +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 +} diff --git a/ctr/events.go b/ctr/events.go index 71cdcf2..b3d59bd 100644 --- a/ctr/events.go +++ b/ctr/events.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "text/tabwriter" + "time" "github.com/codegangsta/cli" "github.com/docker/containerd/api/grpc/types" @@ -13,21 +14,39 @@ import ( 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) { - c := getClient(context) - events, err := c.Events(netcontext.Background(), &types.EventsRequest{}) + var ( + t int64 + 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.Unix() + } + events, err := c.Events(netcontext.Background(), &types.EventsRequest{ + Timestamp: uint64(t), + }) if err != nil { fatal(err.Error(), 1) } w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - fmt.Fprint(w, "TYPE\tID\tPID\tSTATUS\n") + fmt.Fprint(w, "TIME\tTYPE\tID\tPID\tSTATUS\n") w.Flush() for { e, err := events.Recv() if err != nil { fatal(err.Error(), 1) } - fmt.Fprintf(w, "%s\t%s\t%d\t%d\n", e.Type, e.Id, e.Pid, e.Status) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", time.Unix(int64(e.Timestamp), 0).Format(time.RFC3339Nano), e.Type, e.Id, e.Pid, e.Status) w.Flush() } }, diff --git a/ctr/main.go b/ctr/main.go index 3e41d8e..d81960b 100644 --- a/ctr/main.go +++ b/ctr/main.go @@ -31,6 +31,7 @@ func main() { checkpointCommand, containersCommand, eventsCommand, + stateCommand, } app.Before = func(context *cli.Context) error { if context.GlobalBool("debug") { diff --git a/eventloop/eventloop.go b/eventloop/eventloop.go index 7a80a27..0c26495 100644 --- a/eventloop/eventloop.go +++ b/eventloop/eventloop.go @@ -1,9 +1,6 @@ package eventloop -import ( - "runtime" - "sync" -) +import "sync" // Event is receiving notification from loop with Handle() call. type Event interface { @@ -35,8 +32,6 @@ func NewChanLoop(q int) EventLoop { // All calls after first is no-op. func (el *ChanLoop) Start() error { go el.once.Do(func() { - // allocate whole OS thread, so nothing can get scheduled over eventloop - runtime.LockOSThread() for ev := range el.events { ev.Handle() } diff --git a/linux/linux.go b/linux/linux.go deleted file mode 100644 index 0e84798..0000000 --- a/linux/linux.go +++ /dev/null @@ -1,955 +0,0 @@ -// +build libcontainer - -package linux - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - goruntime "runtime" - "strconv" - "strings" - "syscall" - "time" - - "github.com/docker/containerd/runtime" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/configs" - _ "github.com/opencontainers/runc/libcontainer/nsenter" - "github.com/opencontainers/runc/libcontainer/seccomp" - "github.com/opencontainers/specs" -) - -const ( - RLIMIT_CPU = iota // CPU time in sec - RLIMIT_FSIZE // Maximum filesize - RLIMIT_DATA // max data size - RLIMIT_STACK // max stack size - RLIMIT_CORE // max core file size - RLIMIT_RSS // max resident set size - RLIMIT_NPROC // max number of processes - RLIMIT_NOFILE // max number of open files - RLIMIT_MEMLOCK // max locked-in-memory address space - RLIMIT_AS // address space limit - RLIMIT_LOCKS // maximum file locks held - RLIMIT_SIGPENDING // max number of pending signals - RLIMIT_MSGQUEUE // maximum bytes in POSIX mqueues - RLIMIT_NICE // max nice prio allowed to raise to - RLIMIT_RTPRIO // maximum realtime priority - RLIMIT_RTTIME // timeout for RT tasks in us -) - -var rlimitMap = map[string]int{ - "RLIMIT_CPU": RLIMIT_CPU, - "RLIMIT_FSIZE": RLIMIT_FSIZE, - "RLIMIT_DATA": RLIMIT_DATA, - "RLIMIT_STACK": RLIMIT_STACK, - "RLIMIT_CORE": RLIMIT_CORE, - "RLIMIT_RSS": RLIMIT_RSS, - "RLIMIT_NPROC": RLIMIT_NPROC, - "RLIMIT_NOFILE": RLIMIT_NOFILE, - "RLIMIT_MEMLOCK": RLIMIT_MEMLOCK, - "RLIMIT_AS": RLIMIT_AS, - "RLIMIT_LOCKS": RLIMIT_LOCKS, - "RLIMIT_SGPENDING": RLIMIT_SIGPENDING, - "RLIMIT_MSGQUEUE": RLIMIT_MSGQUEUE, - "RLIMIT_NICE": RLIMIT_NICE, - "RLIMIT_RTPRIO": RLIMIT_RTPRIO, - "RLIMIT_RTTIME": RLIMIT_RTTIME, -} - -func strToRlimit(key string) (int, error) { - rl, ok := rlimitMap[key] - if !ok { - return 0, fmt.Errorf("Wrong rlimit value: %s", key) - } - return rl, nil -} - -const wildcard = -1 - -var allowedDevices = []*configs.Device{ - // allow mknod for any device - { - Type: 'c', - Major: wildcard, - Minor: wildcard, - Permissions: "m", - }, - { - Type: 'b', - Major: wildcard, - Minor: wildcard, - Permissions: "m", - }, - { - Path: "/dev/console", - Type: 'c', - Major: 5, - Minor: 1, - Permissions: "rwm", - }, - { - Path: "/dev/tty0", - Type: 'c', - Major: 4, - Minor: 0, - Permissions: "rwm", - }, - { - Path: "/dev/tty1", - Type: 'c', - Major: 4, - Minor: 1, - Permissions: "rwm", - }, - // /dev/pts/ - pts namespaces are "coming soon" - { - Path: "", - Type: 'c', - Major: 136, - Minor: wildcard, - Permissions: "rwm", - }, - { - Path: "", - Type: 'c', - Major: 5, - Minor: 2, - Permissions: "rwm", - }, - // tuntap - { - Path: "", - Type: 'c', - Major: 10, - Minor: 200, - Permissions: "rwm", - }, -} - -var namespaceMapping = map[specs.NamespaceType]configs.NamespaceType{ - specs.PIDNamespace: configs.NEWPID, - specs.NetworkNamespace: configs.NEWNET, - specs.MountNamespace: configs.NEWNS, - specs.UserNamespace: configs.NEWUSER, - specs.IPCNamespace: configs.NEWIPC, - specs.UTSNamespace: configs.NEWUTS, -} - -var mountPropagationMapping = map[string]int{ - "rprivate": syscall.MS_PRIVATE | syscall.MS_REC, - "private": syscall.MS_PRIVATE, - "rslave": syscall.MS_SLAVE | syscall.MS_REC, - "slave": syscall.MS_SLAVE, - "rshared": syscall.MS_SHARED | syscall.MS_REC, - "shared": syscall.MS_SHARED, - "": syscall.MS_PRIVATE | syscall.MS_REC, -} - -func init() { - if len(os.Args) > 1 && os.Args[1] == "init" { - goruntime.GOMAXPROCS(1) - goruntime.LockOSThread() - factory, _ := libcontainer.New("") - if err := factory.StartInitialization(); err != nil { - fmt.Fprint(os.Stderr, err) - os.Exit(1) - } - panic("--this line should have never been executed, congratulations--") - } -} - -type libcontainerProcess struct { - process *libcontainer.Process - spec specs.Process -} - -// change interface to support an error -func (p *libcontainerProcess) Pid() (int, error) { - pid, err := p.process.Pid() - if err != nil { - return -1, err - } - return pid, nil -} - -func (p *libcontainerProcess) Spec() specs.Process { - return p.spec -} - -func (p *libcontainerProcess) Signal(s os.Signal) error { - return p.process.Signal(s) -} - -func (p *libcontainerProcess) Close() error { - // in close we always need to call wait to close/flush any pipes - p.process.Wait() - // explicitly close any open fd on the process - for _, cl := range []interface{}{ - p.process.Stderr, - p.process.Stdout, - p.process.Stdin, - } { - if cl != nil { - if c, ok := cl.(io.Closer); ok { - c.Close() - } - } - } - return nil -} - -type libcontainerContainer struct { - c libcontainer.Container - initProcess *libcontainerProcess - additionalProcesses map[int]*libcontainerProcess - exitStatus int - exited bool - path string -} - -func (c *libcontainerContainer) Checkpoints() ([]runtime.Checkpoint, error) { - out := []runtime.Checkpoint{} - files, err := ioutil.ReadDir(c.getCheckpointPath("")) - if err != nil { - if os.IsNotExist(err) { - return out, nil - } - return nil, err - } - for _, fi := range files { - out = append(out, runtime.Checkpoint{ - Name: fi.Name(), - Timestamp: fi.ModTime(), - }) - } - return out, nil -} - -func (c *libcontainerContainer) DeleteCheckpoint(name string) error { - path := c.getCheckpointPath(name) - if err := os.RemoveAll(path); err != nil { - if os.IsNotExist(err) { - return runtime.ErrCheckpointNotExists - } - return err - } - return nil -} - -func (c *libcontainerContainer) getCheckpointPath(name string) string { - return filepath.Join(c.path, "checkpoints", name) -} - -func (c *libcontainerContainer) Checkpoint(cp runtime.Checkpoint) error { - opts := c.createCheckpointOpts(cp) - if err := os.MkdirAll(filepath.Dir(opts.ImagesDirectory), 0755); err != nil { - return err - } - // mkdir is atomic so if it already exists we can fail - if err := os.Mkdir(opts.ImagesDirectory, 0755); err != nil { - if os.IsExist(err) { - return runtime.ErrCheckpointExists - } - return err - } - if err := c.c.Checkpoint(opts); err != nil { - return err - } - return nil -} - -func (c *libcontainerContainer) createCheckpointOpts(cp runtime.Checkpoint) *libcontainer.CriuOpts { - opts := libcontainer.CriuOpts{} - opts.LeaveRunning = !cp.Exit - opts.ShellJob = cp.Shell - opts.TcpEstablished = cp.Tcp - opts.ExternalUnixConnections = cp.UnixSockets - opts.ImagesDirectory = c.getCheckpointPath(cp.Name) - return &opts -} - -func (c *libcontainerContainer) Restore(name string) error { - path := c.getCheckpointPath(name) - var opts libcontainer.CriuOpts - opts.ImagesDirectory = path - return c.c.Restore(c.initProcess.process, &opts) -} - -func (c *libcontainerContainer) Resume() error { - return c.c.Resume() -} - -func (c *libcontainerContainer) Pause() error { - return c.c.Pause() -} - -func (c *libcontainerContainer) State() runtime.State { - // TODO: what to do with error - state, err := c.c.Status() - if err != nil { - return runtime.State("") - } - switch state { - case libcontainer.Paused, libcontainer.Pausing: - return runtime.Paused - } - return runtime.State("") -} - -func (c *libcontainerContainer) ID() string { - return c.c.ID() -} - -func (c *libcontainerContainer) Path() string { - return c.path -} - -func (c *libcontainerContainer) Pid() (int, error) { - return c.initProcess.Pid() -} - -func (c *libcontainerContainer) Start() error { - return c.c.Start(c.initProcess.process) -} - -func (c *libcontainerContainer) SetExited(status int) { - c.exitStatus = status - // meh - c.exited = true - c.initProcess.Close() -} - -func (c *libcontainerContainer) Stats() (*runtime.Stat, error) { - now := time.Now() - stats, err := c.c.Stats() - if err != nil { - return nil, err - } - return &runtime.Stat{ - Timestamp: now, - Data: stats, - }, nil -} - -func (c *libcontainerContainer) Delete() error { - return c.c.Destroy() -} - -func (c *libcontainerContainer) Processes() ([]runtime.Process, error) { - procs := []runtime.Process{ - c.initProcess, - } - for _, p := range c.additionalProcesses { - procs = append(procs, p) - } - return procs, nil -} - -func (c *libcontainerContainer) RemoveProcess(pid int) error { - proc, ok := c.additionalProcesses[pid] - if !ok { - return runtime.ErrNotChildProcess - } - err := proc.Close() - delete(c.additionalProcesses, pid) - return err -} - -func (c *libcontainerContainer) OOM() (<-chan struct{}, error) { - return c.c.NotifyOOM() -} - -func NewRuntime(stateDir string) (runtime.Runtime, error) { - f, err := libcontainer.New(stateDir, libcontainer.Cgroupfs, func(l *libcontainer.LinuxFactory) error { - //l.CriuPath = context.GlobalString("criu") - return nil - }) - if err != nil { - return nil, err - } - return &libcontainerRuntime{ - factory: f, - }, nil -} - -type libcontainerRuntime struct { - factory libcontainer.Factory -} - -func (r *libcontainerRuntime) Type() string { - return "libcontainer" -} - -func (r *libcontainerRuntime) Create(id, bundlePath, consolePath string) (runtime.Container, *runtime.IO, error) { - spec, rspec, err := r.loadSpec( - filepath.Join(bundlePath, "config.json"), - filepath.Join(bundlePath, "runtime.json"), - ) - if err != nil { - return nil, nil, err - } - config, err := r.createLibcontainerConfig(id, bundlePath, spec, rspec) - if err != nil { - return nil, nil, err - } - container, err := r.factory.Create(id, config) - if err != nil { - return nil, nil, fmt.Errorf("create container: %v", err) - } - process, err := r.newProcess(spec.Process) - if err != nil { - return nil, nil, err - } - var rio runtime.IO - if spec.Process.Terminal { - if err := process.ConsoleFromPath(consolePath); err != nil { - return nil, nil, err - } - } else { - uid, err := config.HostUID() - if err != nil { - return nil, nil, err - } - i, err := process.InitializeIO(uid) - if err != nil { - return nil, nil, err - } - rio.Stdin = i.Stdin - rio.Stderr = i.Stderr - rio.Stdout = i.Stdout - } - c := &libcontainerContainer{ - c: container, - additionalProcesses: make(map[int]*libcontainerProcess), - initProcess: &libcontainerProcess{ - process: process, - spec: spec.Process, - }, - path: bundlePath, - } - return c, &rio, nil -} - -func (r *libcontainerRuntime) StartProcess(ci runtime.Container, p specs.Process, consolePath string) (runtime.Process, *runtime.IO, error) { - c, ok := ci.(*libcontainerContainer) - if !ok { - return nil, nil, runtime.ErrInvalidContainerType - } - process, err := r.newProcess(p) - if err != nil { - return nil, nil, err - } - var rio runtime.IO - if p.Terminal { - if err := process.ConsoleFromPath(consolePath); err != nil { - return nil, nil, err - } - } else { - uid, err := c.c.Config().HostUID() - if err != nil { - return nil, nil, err - } - i, err := process.InitializeIO(uid) - if err != nil { - return nil, nil, err - } - rio.Stdin = i.Stdin - rio.Stderr = i.Stderr - rio.Stdout = i.Stdout - } - if err := c.c.Start(process); err != nil { - return nil, nil, err - } - lp := &libcontainerProcess{ - process: process, - spec: p, - } - pid, err := process.Pid() - if err != nil { - return nil, nil, err - } - c.additionalProcesses[pid] = lp - return lp, &rio, nil -} - -// newProcess returns a new libcontainer Process with the arguments from the -// spec and stdio from the current process. -func (r *libcontainerRuntime) newProcess(p specs.Process) (*libcontainer.Process, error) { - return &libcontainer.Process{ - Args: p.Args, - Env: p.Env, - // TODO: fix libcontainer's API to better support uid/gid in a typesafe way. - User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID), - Cwd: p.Cwd, - }, nil -} - -// loadSpec loads the specification from the provided path. -// If the path is empty then the default path will be "config.json" -func (r *libcontainerRuntime) loadSpec(cPath, rPath string) (spec *specs.LinuxSpec, rspec *specs.LinuxRuntimeSpec, err error) { - cf, err := os.Open(cPath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil, fmt.Errorf("JSON specification file at %s not found", cPath) - } - return spec, rspec, err - } - defer cf.Close() - - rf, err := os.Open(rPath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil, fmt.Errorf("JSON runtime config file at %s not found", rPath) - } - return spec, rspec, err - } - defer rf.Close() - - if err = json.NewDecoder(cf).Decode(&spec); err != nil { - return spec, rspec, fmt.Errorf("unmarshal %s: %v", cPath, err) - } - if err = json.NewDecoder(rf).Decode(&rspec); err != nil { - return spec, rspec, fmt.Errorf("unmarshal %s: %v", rPath, err) - } - return spec, rspec, r.checkSpecVersion(spec) -} - -// checkSpecVersion makes sure that the spec version matches runc's while we are in the initial -// development period. It is better to hard fail than have missing fields or options in the spec. -func (r *libcontainerRuntime) checkSpecVersion(s *specs.LinuxSpec) error { - if s.Version != specs.Version { - return fmt.Errorf("spec version is not compatible with implemented version %q: spec %q", specs.Version, s.Version) - } - return nil -} - -func (r *libcontainerRuntime) createLibcontainerConfig(cgroupName, bundlePath string, spec *specs.LinuxSpec, rspec *specs.LinuxRuntimeSpec) (*configs.Config, error) { - rootfsPath := spec.Root.Path - if !filepath.IsAbs(rootfsPath) { - rootfsPath = filepath.Join(bundlePath, rootfsPath) - } - config := &configs.Config{ - Rootfs: rootfsPath, - Capabilities: spec.Linux.Capabilities, - Readonlyfs: spec.Root.Readonly, - Hostname: spec.Hostname, - } - for _, ns := range rspec.Linux.Namespaces { - t, exists := namespaceMapping[ns.Type] - if !exists { - return nil, fmt.Errorf("namespace %q does not exist", ns) - } - config.Namespaces.Add(t, ns.Path) - } - if config.Namespaces.Contains(configs.NEWNET) { - config.Networks = []*configs.Network{ - { - Type: "loopback", - }, - } - } - for _, mp := range spec.Mounts { - m, ok := rspec.Mounts[mp.Name] - if !ok { - return nil, fmt.Errorf("Mount with Name %q not found in runtime config", mp.Name) - } - config.Mounts = append(config.Mounts, r.createLibcontainerMount(bundlePath, mp.Path, m)) - } - - // Convert rootfs propagation flag - if rspec.Linux.RootfsPropagation != "" { - _, pflags, _ := parseMountOptions([]string{rspec.Linux.RootfsPropagation}) - if len(pflags) == 1 { - config.RootPropagation = pflags[0] - } - } - - if err := r.createDevices(rspec, config); err != nil { - return nil, err - } - if err := r.setupUserNamespace(rspec, config); err != nil { - return nil, err - } - for _, rlimit := range rspec.Linux.Rlimits { - rl, err := r.createLibContainerRlimit(rlimit) - if err != nil { - return nil, err - } - config.Rlimits = append(config.Rlimits, rl) - } - c, err := r.createCgroupConfig(cgroupName, rspec, config.Devices) - if err != nil { - return nil, err - } - config.Cgroups = c - if config.Readonlyfs { - r.setReadonly(config) - config.MaskPaths = []string{ - "/proc/kcore", - } - config.ReadonlyPaths = []string{ - "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", - } - } - seccomp, err := r.setupSeccomp(&rspec.Linux.Seccomp) - if err != nil { - return nil, err - } - config.Seccomp = seccomp - config.Sysctl = rspec.Linux.Sysctl - config.ProcessLabel = rspec.Linux.SelinuxProcessLabel - config.AppArmorProfile = rspec.Linux.ApparmorProfile - for _, g := range spec.Process.User.AdditionalGids { - config.AdditionalGroups = append(config.AdditionalGroups, strconv.FormatUint(uint64(g), 10)) - } - r.createHooks(rspec, config) - config.Version = specs.Version - return config, nil -} - -func (r *libcontainerRuntime) createLibcontainerMount(cwd, dest string, m specs.Mount) *configs.Mount { - flags, pgflags, data := parseMountOptions(m.Options) - source := m.Source - if m.Type == "bind" { - if !filepath.IsAbs(source) { - source = filepath.Join(cwd, m.Source) - } - } - return &configs.Mount{ - Device: m.Type, - Source: source, - Destination: dest, - Data: data, - Flags: flags, - PropagationFlags: pgflags, - } -} - -func (rt *libcontainerRuntime) createCgroupConfig(name string, spec *specs.LinuxRuntimeSpec, devices []*configs.Device) (*configs.Cgroup, error) { - cr := &configs.Cgroup{ - Name: name, - Parent: "/containerd", - } - c := &configs.Resources{ - AllowedDevices: append(devices, allowedDevices...), - } - cr.Resources = c - r := spec.Linux.Resources - if r.Memory != nil { - if r.Memory.Limit != nil { - c.Memory = int64(*r.Memory.Limit) - } - if r.Memory.Reservation != nil { - c.MemoryReservation = int64(*r.Memory.Reservation) - } - if r.Memory.Swap != nil { - c.MemorySwap = int64(*r.Memory.Swap) - } - if r.Memory.Kernel != nil { - c.KernelMemory = int64(*r.Memory.Kernel) - } - if r.Memory.Swappiness != nil { - c.MemorySwappiness = int64(*r.Memory.Swappiness) - } - } - if r.CPU != nil { - if r.CPU.Shares != nil { - c.CpuShares = int64(*r.CPU.Shares) - } - if r.CPU.Quota != nil { - c.CpuQuota = int64(*r.CPU.Quota) - } - if r.CPU.Period != nil { - c.CpuPeriod = int64(*r.CPU.Period) - } - if r.CPU.RealtimeRuntime != nil { - c.CpuRtRuntime = int64(*r.CPU.RealtimeRuntime) - } - if r.CPU.RealtimePeriod != nil { - c.CpuRtPeriod = int64(*r.CPU.RealtimePeriod) - } - if r.CPU.Cpus != nil { - c.CpusetCpus = *r.CPU.Cpus - } - if r.CPU.Mems != nil { - c.CpusetMems = *r.CPU.Mems - } - } - if r.BlockIO != nil { - if r.BlockIO.Weight != nil { - c.BlkioWeight = *r.BlockIO.Weight - } - if r.BlockIO.LeafWeight != nil { - c.BlkioLeafWeight = *r.BlockIO.LeafWeight - } - } - for _, wd := range r.BlockIO.WeightDevice { - weightDevice := configs.NewWeightDevice(wd.Major, wd.Minor, *wd.Weight, *wd.LeafWeight) - c.BlkioWeightDevice = append(c.BlkioWeightDevice, weightDevice) - } - for _, td := range r.BlockIO.ThrottleReadBpsDevice { - throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) - c.BlkioThrottleReadBpsDevice = append(c.BlkioThrottleReadBpsDevice, throttleDevice) - } - for _, td := range r.BlockIO.ThrottleWriteBpsDevice { - throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) - c.BlkioThrottleWriteBpsDevice = append(c.BlkioThrottleWriteBpsDevice, throttleDevice) - } - for _, td := range r.BlockIO.ThrottleReadIOPSDevice { - throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) - c.BlkioThrottleReadIOPSDevice = append(c.BlkioThrottleReadIOPSDevice, throttleDevice) - } - for _, td := range r.BlockIO.ThrottleWriteIOPSDevice { - throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) - c.BlkioThrottleWriteIOPSDevice = append(c.BlkioThrottleWriteIOPSDevice, throttleDevice) - } - for _, l := range r.HugepageLimits { - c.HugetlbLimit = append(c.HugetlbLimit, &configs.HugepageLimit{ - Pagesize: *l.Pagesize, - Limit: *l.Limit, - }) - } - c.OomKillDisable = r.DisableOOMKiller != nil && *r.DisableOOMKiller - if r.Network != nil { - c.NetClsClassid = r.Network.ClassID - for _, m := range r.Network.Priorities { - c.NetPrioIfpriomap = append(c.NetPrioIfpriomap, &configs.IfPrioMap{ - Interface: m.Name, - Priority: int64(m.Priority), - }) - } - } - return cr, nil -} - -func (r *libcontainerRuntime) createDevices(spec *specs.LinuxRuntimeSpec, config *configs.Config) error { - for _, d := range spec.Linux.Devices { - device := &configs.Device{ - Type: d.Type, - Path: d.Path, - Major: d.Major, - Minor: d.Minor, - Permissions: d.Permissions, - FileMode: d.FileMode, - Uid: d.UID, - Gid: d.GID, - } - config.Devices = append(config.Devices, device) - } - return nil -} - -func (r *libcontainerRuntime) setReadonly(config *configs.Config) { - for _, m := range config.Mounts { - if m.Device == "sysfs" { - m.Flags |= syscall.MS_RDONLY - } - } -} - -func (r *libcontainerRuntime) setupUserNamespace(spec *specs.LinuxRuntimeSpec, config *configs.Config) error { - if len(spec.Linux.UIDMappings) == 0 { - return nil - } - config.Namespaces.Add(configs.NEWUSER, "") - create := func(m specs.IDMapping) configs.IDMap { - return configs.IDMap{ - HostID: int(m.HostID), - ContainerID: int(m.ContainerID), - Size: int(m.Size), - } - } - for _, m := range spec.Linux.UIDMappings { - config.UidMappings = append(config.UidMappings, create(m)) - } - for _, m := range spec.Linux.GIDMappings { - config.GidMappings = append(config.GidMappings, create(m)) - } - rootUID, err := config.HostUID() - if err != nil { - return err - } - rootGID, err := config.HostGID() - if err != nil { - return err - } - for _, node := range config.Devices { - node.Uid = uint32(rootUID) - node.Gid = uint32(rootGID) - } - return nil -} - -func (r *libcontainerRuntime) createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) { - rl, err := strToRlimit(rlimit.Type) - if err != nil { - return configs.Rlimit{}, err - } - return configs.Rlimit{ - Type: rl, - Hard: uint64(rlimit.Hard), - Soft: uint64(rlimit.Soft), - }, nil -} - -// parseMountOptions parses the string and returns the flags, propagation -// flags and any mount data that it contains. -func parseMountOptions(options []string) (int, []int, string) { - var ( - flag int - pgflag []int - data []string - ) - flags := map[string]struct { - clear bool - flag int - }{ - "async": {true, syscall.MS_SYNCHRONOUS}, - "atime": {true, syscall.MS_NOATIME}, - "bind": {false, syscall.MS_BIND}, - "defaults": {false, 0}, - "dev": {true, syscall.MS_NODEV}, - "diratime": {true, syscall.MS_NODIRATIME}, - "dirsync": {false, syscall.MS_DIRSYNC}, - "exec": {true, syscall.MS_NOEXEC}, - "mand": {false, syscall.MS_MANDLOCK}, - "noatime": {false, syscall.MS_NOATIME}, - "nodev": {false, syscall.MS_NODEV}, - "nodiratime": {false, syscall.MS_NODIRATIME}, - "noexec": {false, syscall.MS_NOEXEC}, - "nomand": {true, syscall.MS_MANDLOCK}, - "norelatime": {true, syscall.MS_RELATIME}, - "nostrictatime": {true, syscall.MS_STRICTATIME}, - "nosuid": {false, syscall.MS_NOSUID}, - "rbind": {false, syscall.MS_BIND | syscall.MS_REC}, - "relatime": {false, syscall.MS_RELATIME}, - "remount": {false, syscall.MS_REMOUNT}, - "ro": {false, syscall.MS_RDONLY}, - "rw": {true, syscall.MS_RDONLY}, - "strictatime": {false, syscall.MS_STRICTATIME}, - "suid": {true, syscall.MS_NOSUID}, - "sync": {false, syscall.MS_SYNCHRONOUS}, - } - propagationFlags := map[string]struct { - clear bool - flag int - }{ - "private": {false, syscall.MS_PRIVATE}, - "shared": {false, syscall.MS_SHARED}, - "slave": {false, syscall.MS_SLAVE}, - "unbindable": {false, syscall.MS_UNBINDABLE}, - "rprivate": {false, syscall.MS_PRIVATE | syscall.MS_REC}, - "rshared": {false, syscall.MS_SHARED | syscall.MS_REC}, - "rslave": {false, syscall.MS_SLAVE | syscall.MS_REC}, - "runbindable": {false, syscall.MS_UNBINDABLE | syscall.MS_REC}, - } - for _, o := range options { - // If the option does not exist in the flags table or the flag - // is not supported on the platform, - // then it is a data value for a specific fs type - if f, exists := flags[o]; exists && f.flag != 0 { - if f.clear { - flag &= ^f.flag - } else { - flag |= f.flag - } - } else if f, exists := propagationFlags[o]; exists && f.flag != 0 { - pgflag = append(pgflag, f.flag) - } else { - data = append(data, o) - } - } - return flag, pgflag, strings.Join(data, ",") -} - -func (r *libcontainerRuntime) setupSeccomp(config *specs.Seccomp) (*configs.Seccomp, error) { - if config == nil { - return nil, nil - } - - // No default action specified, no syscalls listed, assume seccomp disabled - if config.DefaultAction == "" && len(config.Syscalls) == 0 { - return nil, nil - } - - newConfig := new(configs.Seccomp) - newConfig.Syscalls = []*configs.Syscall{} - - if len(config.Architectures) > 0 { - newConfig.Architectures = []string{} - for _, arch := range config.Architectures { - newArch, err := seccomp.ConvertStringToArch(string(arch)) - if err != nil { - return nil, err - } - newConfig.Architectures = append(newConfig.Architectures, newArch) - } - } - - // Convert default action from string representation - newDefaultAction, err := seccomp.ConvertStringToAction(string(config.DefaultAction)) - if err != nil { - return nil, err - } - newConfig.DefaultAction = newDefaultAction - - // Loop through all syscall blocks and convert them to libcontainer format - for _, call := range config.Syscalls { - newAction, err := seccomp.ConvertStringToAction(string(call.Action)) - if err != nil { - return nil, err - } - - newCall := configs.Syscall{ - Name: call.Name, - Action: newAction, - Args: []*configs.Arg{}, - } - - // Loop through all the arguments of the syscall and convert them - for _, arg := range call.Args { - newOp, err := seccomp.ConvertStringToOperator(string(arg.Op)) - if err != nil { - return nil, err - } - - newArg := configs.Arg{ - Index: arg.Index, - Value: arg.Value, - ValueTwo: arg.ValueTwo, - Op: newOp, - } - - newCall.Args = append(newCall.Args, &newArg) - } - - newConfig.Syscalls = append(newConfig.Syscalls, &newCall) - } - - return newConfig, nil -} - -func (r *libcontainerRuntime) createHooks(rspec *specs.LinuxRuntimeSpec, config *configs.Config) { - config.Hooks = &configs.Hooks{} - for _, h := range rspec.Hooks.Prestart { - cmd := configs.Command{ - Path: h.Path, - Args: h.Args, - Env: h.Env, - } - config.Hooks.Prestart = append(config.Hooks.Prestart, configs.NewCommandHook(cmd)) - } - for _, h := range rspec.Hooks.Poststop { - cmd := configs.Command{ - Path: h.Path, - Args: h.Args, - Env: h.Env, - } - config.Hooks.Poststop = append(config.Hooks.Poststop, configs.NewCommandHook(cmd)) - } -} diff --git a/runc/runc.go b/runc/runc.go deleted file mode 100644 index 75f48b1..0000000 --- a/runc/runc.go +++ /dev/null @@ -1,235 +0,0 @@ -// +build runc - -package runc - -import ( - "encoding/json" - "errors" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - - "github.com/docker/containerd/runtime" - "github.com/opencontainers/specs" -) - -func NewRuntime(stateDir string) (runtime.Runtime, error) { - return &runcRuntime{ - stateDir: stateDir, - }, nil -} - -type runcContainer struct { - id string - path string - stateDir string - exitStatus int - processes map[int]*runcProcess - initProcess *runcProcess -} - -func (c *runcContainer) ID() string { - return c.id -} - -func (c *runcContainer) Start() error { - return c.initProcess.cmd.Start() -} - -func (c *runcContainer) Stats() (*runtime.Stat, error) { - return nil, errors.New("containerd: runc does not support stats in containerd") -} - -func (c *runcContainer) Path() string { - return c.path -} - -func (c *runcContainer) Pid() (int, error) { - return c.initProcess.cmd.Process.Pid, nil -} - -func (c *runcContainer) SetExited(status int) { - c.exitStatus = status -} - -// noop for runc -func (c *runcContainer) Delete() error { - return nil -} - -func (c *runcContainer) Processes() ([]runtime.Process, error) { - procs := []runtime.Process{ - c.initProcess, - } - for _, p := range c.processes { - procs = append(procs, p) - } - return procs, nil -} - -func (c *runcContainer) RemoveProcess(pid int) error { - if _, ok := c.processes[pid]; !ok { - return runtime.ErrNotChildProcess - } - delete(c.processes, pid) - return nil -} - -func (c *runcContainer) State() runtime.State { - // TODO: how to do this with runc - return runtime.State{ - Status: runtime.Running, - } -} - -func (c *runcContainer) Resume() error { - return c.newCommand("resume").Run() -} - -func (c *runcContainer) Pause() error { - return c.newCommand("pause").Run() -} - -// TODO: pass arguments -func (c *runcContainer) Checkpoint(runtime.Checkpoint) error { - return c.newCommand("checkpoint").Run() -} - -// TODO: pass arguments -func (c *runcContainer) Restore(cp string) error { - return c.newCommand("restore").Run() -} - -// TODO: pass arguments -func (c *runcContainer) DeleteCheckpoint(cp string) error { - return errors.New("not implemented") -} - -// TODO: implement in runc -func (c *runcContainer) Checkpoints() ([]runtime.Checkpoint, error) { - return nil, errors.New("not implemented") -} - -func (c *runcContainer) OOM() (<-chan struct{}, error) { - return nil, errors.New("not implemented") -} - -func (c *runcContainer) newCommand(args ...string) *exec.Cmd { - cmd := exec.Command("runc", append([]string{"--root", c.stateDir, "--id", c.id}, args...)...) - cmd.Dir = c.path - return cmd -} - -type runcProcess struct { - cmd *exec.Cmd - spec specs.Process -} - -// pid of the container, not of runc -func (p *runcProcess) Pid() (int, error) { - return p.cmd.Process.Pid, nil -} - -func (p *runcProcess) Spec() specs.Process { - return p.spec -} - -func (p *runcProcess) Signal(s os.Signal) error { - return p.cmd.Process.Signal(s) -} - -func (p *runcProcess) Close() error { - return nil -} - -type runcRuntime struct { - stateDir string -} - -func (r *runcRuntime) Type() string { - return "runc" -} - -func (r *runcRuntime) Create(id, bundlePath, consolePath string) (runtime.Container, *runtime.IO, error) { - var s specs.Spec - f, err := os.Open(filepath.Join(bundlePath, "config.json")) - if err != nil { - return nil, nil, err - } - defer f.Close() - - if err := json.NewDecoder(f).Decode(&s); err != nil { - return nil, nil, err - } - cmd := exec.Command("runc", "--root", r.stateDir, "--id", id, "start") - cmd.Dir = bundlePath - i, err := r.createIO(cmd) - if err != nil { - return nil, nil, err - } - return &runcContainer{ - id: id, - path: bundlePath, - stateDir: r.stateDir, - initProcess: &runcProcess{ - cmd: cmd, - spec: s.Process, - }, - processes: make(map[int]*runcProcess), - }, i, nil -} - -func (r *runcRuntime) createIO(cmd *exec.Cmd) (*runtime.IO, error) { - w, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - ro, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - re, err := cmd.StderrPipe() - if err != nil { - return nil, err - } - return &runtime.IO{ - Stdin: w, - Stdout: ro, - Stderr: re, - }, nil -} - -func (r *runcRuntime) StartProcess(ci runtime.Container, p specs.Process, consolePath string) (runtime.Process, *runtime.IO, error) { - c, ok := ci.(*runcContainer) - if !ok { - return nil, nil, runtime.ErrInvalidContainerType - } - f, err := ioutil.TempFile("", "containerd") - if err != nil { - return nil, nil, err - } - err = json.NewEncoder(f).Encode(p) - f.Close() - if err != nil { - return nil, nil, err - } - cmd := c.newCommand("exec", f.Name()) - i, err := r.createIO(cmd) - if err != nil { - return nil, nil, err - } - process := &runcProcess{ - cmd: cmd, - spec: p, - } - if err := cmd.Start(); err != nil { - return nil, nil, err - } - pid, err := process.Pid() - if err != nil { - return nil, nil, err - } - c.processes[pid] = process - return process, i, nil -} diff --git a/runtime/container.go b/runtime/container.go index a725dbd..a8795c4 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -1,112 +1,418 @@ package runtime import ( - "io" + "encoding/json" + "io/ioutil" "os" + "os/exec" + "path/filepath" + "syscall" "time" + "github.com/Sirupsen/logrus" + "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/specs" ) -type Process interface { - io.Closer - Pid() (int, error) - Spec() specs.Process - Signal(os.Signal) error -} - -type State string - -const ( - Paused = State("paused") - Running = State("running") -) - -type Console interface { - io.ReadWriter - io.Closer -} - -type IO struct { - Stdin io.WriteCloser - Stdout io.ReadCloser - Stderr io.ReadCloser -} - -func (i *IO) Close() error { - var oerr error - for _, c := range []io.Closer{ - i.Stdin, - i.Stdout, - i.Stderr, - } { - if c != nil { - if err := c.Close(); oerr == nil { - oerr = err - } - } - } - return oerr -} - -type Stat struct { - // Timestamp is the time that the statistics where collected - Timestamp time.Time - // Data is the raw stats - // TODO: it is currently an interface because we don't know what type of exec drivers - // we will have or what the structure should look like at the moment os the containers - // can return what they want and we could marshal to json or whatever. - Data interface{} -} - -type Checkpoint struct { - // Timestamp is the time that checkpoint happened - Timestamp time.Time - // Name is the name of the checkpoint - Name string - // Tcp checkpoints open tcp connections - Tcp bool - // UnixSockets persists unix sockets in the checkpoint - UnixSockets bool - // Shell persists tty sessions in the checkpoint - Shell bool - // Exit exits the container after the checkpoint is finished - Exit bool -} - type Container interface { // ID returns the container ID ID() string - // Start starts the init process of the container - Start() error // Path returns the path to the bundle Path() string - // Pid returns the container's init process id - Pid() (int, error) - // SetExited sets the exit status of the container after its init dies - SetExited(status int) - // Delete deletes the container + // Start starts the init process of the container + Start(checkpoint string, s Stdio) (Process, error) + // Exec starts another process in an existing container + Exec(string, specs.Process, 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) - // RemoveProcess removes a specific process for the container because it exited - RemoveProcess(pid int) 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() ([]Checkpoint, error) // Checkpoint creates a new checkpoint Checkpoint(Checkpoint) error // DeleteCheckpoint deletes the checkpoint for the provided name DeleteCheckpoint(name string) error - // Restore restores the container to that of the checkpoint provided by name - Restore(name 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) // OOM signals the channel if the container received an OOM notification - OOM() (<-chan struct{}, error) + // OOM() (<-chan struct{}, error) +} + +type Stdio struct { + Stdin string + Stdout string + Stderr string +} + +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, + } +} + +// New returns a new container +func New(root, id, bundle string, labels []string) (Container, error) { + c := &container{ + root: root, + id: id, + bundle: bundle, + labels: labels, + processes: make(map[string]*process), + } + if err := os.Mkdir(filepath.Join(root, id), 0755); err != nil { + return nil, err + } + f, err := os.Create(filepath.Join(root, id, StateFile)) + if err != nil { + return nil, err + } + defer f.Close() + if err := json.NewEncoder(f).Encode(state{ + Bundle: bundle, + Labels: labels, + }); err != nil { + return nil, err + } + return c, nil +} + +func Load(root, id string) (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, + processes: make(map[string]*process), + } + 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 + processes map[string]*process + stdio Stdio + labels []string +} + +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) Start(checkpoint 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("containerd-shim", + c.id, c.bundle, + ) + cmd.Dir = processRoot + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + spec, err := c.readSpec() + if err != nil { + return nil, err + } + config := &processConfig{ + checkpoint: checkpoint, + root: processRoot, + id: InitProcessID, + c: c, + stdio: s, + spec: spec, + processSpec: spec.Process, + } + p, err := newProcess(config) + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + if _, err := p.getPid(); err != nil { + return p, nil + } + c.processes[InitProcessID] = p + return p, nil +} + +func (c *container) Exec(pid string, spec specs.Process, s Stdio) (Process, error) { + processRoot := filepath.Join(c.root, c.id, pid) + if err := os.Mkdir(processRoot, 0755); err != nil { + return nil, err + } + cmd := exec.Command("containerd-shim", + c.id, c.bundle, + ) + cmd.Dir = processRoot + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + config := &processConfig{ + exec: true, + id: pid, + root: processRoot, + c: c, + processSpec: spec, + stdio: s, + } + p, err := newProcess(config) + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + if _, err := p.getPid(); err != nil { + return p, nil + } + c.processes[pid] = p + return p, nil +} + +func (c *container) readSpec() (*specs.LinuxSpec, error) { + var spec specs.LinuxSpec + 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) Pause() error { + return exec.Command("runc", "pause", c.id).Run() +} + +func (c *container) Resume() error { + return exec.Command("runc", "resume", c.id).Run() +} + +func (c *container) State() State { + return Running +} + +func (c *container) Delete() error { + return os.RemoveAll(filepath.Join(c.root, c.id)) +} + +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) Checkpoints() ([]Checkpoint, error) { + dirs, err := ioutil.ReadDir(filepath.Join(c.bundle, "checkpoints")) + if err != nil { + return nil, err + } + var out []Checkpoint + for _, d := range dirs { + if !d.IsDir() { + continue + } + path := filepath.Join(c.bundle, "checkpoints", 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) error { + if err := os.MkdirAll(filepath.Join(c.bundle, "checkpoints"), 0755); err != nil { + return err + } + path := filepath.Join(c.bundle, "checkpoints", 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, + } + add := func(flags ...string) { + args = append(args, flags...) + } + 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") + } + add(c.id) + return exec.Command("runc", args...).Run() +} + +func (c *container) DeleteCheckpoint(name string) error { + return os.RemoveAll(filepath.Join(c.bundle, "checkpoints", name)) +} + +func (c *container) Pids() ([]int, error) { + container, err := c.getLibctContainer() + if err != nil { + return nil, err + } + return container.Processes() +} + +func (c *container) Stats() (*Stat, error) { + container, err := c.getLibctContainer() + if err != nil { + return nil, err + } + now := time.Now() + stats, err := container.Stats() + if err != nil { + return nil, err + } + return &Stat{ + Timestamp: now, + Data: stats, + }, nil +} + +func (c *container) getLibctContainer() (libcontainer.Container, error) { + f, err := libcontainer.New(specs.LinuxStateDirectory, libcontainer.Cgroupfs) + if err != nil { + return nil, err + } + return f.Load(c.id) +} + +func getRootIDs(s *specs.LinuxSpec) (int, int, error) { + if s == nil { + return 0, 0, nil + } + var hasUserns bool + for _, ns := range s.Linux.Namespaces { + if ns.Type == specs.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 hostIDFromMap(id uint32, mp []specs.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 } diff --git a/runtime/process.go b/runtime/process.go new file mode 100644 index 0000000..3aa5654 --- /dev/null +++ b/runtime/process.go @@ -0,0 +1,229 @@ +package runtime + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "syscall" + "time" + + "github.com/opencontainers/specs" +) + +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 + CloseStdin() error + Resize(int, int) error + // ExitFD returns the fd the provides an event when the process exits + ExitFD() int + // ExitStatus returns the exit status of the process or an error if it + // has not exited + ExitStatus() (int, error) + // Spec returns the process spec that created the process + Spec() specs.Process + // 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 +} + +type processConfig struct { + id string + root string + processSpec specs.Process + spec *specs.LinuxSpec + 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, + } + 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() + if err := json.NewEncoder(f).Encode(ProcessState{ + Process: config.processSpec, + Exec: config.exec, + Checkpoint: config.checkpoint, + RootUID: uid, + RootGID: gid, + Stdin: config.stdio.Stdin, + Stdout: config.stdio.Stdout, + Stderr: config.stdio.Stderr, + }); 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.Process, + stdio: Stdio{ + Stdin: s.Stdin, + Stdout: s.Stdout, + Stderr: s.Stderr, + }, + } + if _, err := p.getPid(); 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 + return p, nil + } + return nil, err + } + return p, nil +} + +func getExitPipe(path string) (*os.File, error) { + if err := syscall.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 := syscall.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0) +} + +type process struct { + root string + id string + pid int + exitPipe *os.File + controlPipe *os.File + container *container + spec specs.Process + stdio Stdio +} + +func (p *process) ID() string { + return p.id +} + +func (p *process) Container() Container { + return p.container +} + +func (p *process) SystemPid() int { + return p.pid +} + +// ExitFD returns the fd of the exit pipe +func (p *process) ExitFD() 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) ExitStatus() (int, error) { + data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile)) + if err != nil { + if os.IsNotExist(err) { + return -1, ErrProcessNotExited + } + return -1, err + } + if len(data) == 0 { + return -1, ErrProcessNotExited + } + return strconv.Atoi(string(data)) +} + +// Signal sends the provided signal to the process +func (p *process) Signal(s os.Signal) error { + return syscall.Kill(p.pid, s.(syscall.Signal)) +} + +func (p *process) Spec() specs.Process { + 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 { + return p.exitPipe.Close() +} + +func (p *process) getPid() (int, error) { + for i := 0; i < 20; i++ { + data, err := ioutil.ReadFile(filepath.Join(p.root, "pid")) + if err != nil { + if os.IsNotExist(err) { + time.Sleep(100 * time.Millisecond) + continue + } + return -1, err + } + i, err := strconv.Atoi(string(data)) + if err != nil { + return -1, err + } + p.pid = i + return i, nil + } + return -1, fmt.Errorf("containerd: cannot read pid file") +} diff --git a/runtime/runtime.go b/runtime/runtime.go index 60abff6..7ab9bc5 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -2,6 +2,7 @@ package runtime import ( "errors" + "time" "github.com/opencontainers/specs" ) @@ -13,14 +14,67 @@ var ( ErrCheckpointExists = errors.New("containerd: checkpoint already exists") ErrContainerExited = errors.New("containerd: container has exited") ErrTerminalsNotSupported = errors.New("containerd: terminals are not supported for runtime") + ErrProcessNotExited = errors.New("containerd: process has not exited") + ErrProcessExited = errors.New("containerd: process has exited") + + errNotImplemented = errors.New("containerd: not implemented") ) -// Runtime handles containers, containers handle their own actions -type Runtime interface { - // Type of the runtime - Type() string - // Create creates a new container initialized but without it starting it - Create(id, bundlePath, consolePath string) (Container, *IO, error) - // StartProcess adds a new process to the container - StartProcess(c Container, p specs.Process, consolePath string) (Process, *IO, error) +const ( + ExitFile = "exit" + ExitStatusFile = "exitStatus" + StateFile = "state.json" + ControlFile = "control" + InitProcessID = "init" +) + +type State string + +const ( + Paused = State("paused") + 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"` +} + +type ProcessState struct { + specs.Process + Exec bool `json:"exec"` + Checkpoint string `json:"checkpoint"` + RootUID int `json:"rootUID"` + RootGID int `json:"rootGID"` + Stdin string `json:"containerdStdin"` + Stdout string `json:"containerdStdout"` + Stderr string `json:"containerdStderr"` +} + +type Stat struct { + // Timestamp is the time that the statistics where collected + Timestamp time.Time + // Data is the raw stats + // TODO: it is currently an interface because we don't know what type of exec drivers + // we will have or what the structure should look like at the moment os the containers + // can return what they want and we could marshal to json or whatever. + Data interface{} +} + +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"` } diff --git a/supervisor/add_process.go b/supervisor/add_process.go index bc0efb9..6801a21 100644 --- a/supervisor/add_process.go +++ b/supervisor/add_process.go @@ -3,40 +3,35 @@ package supervisor import ( "time" - "github.com/Sirupsen/logrus" + "github.com/docker/containerd/runtime" ) -type AddProcessEvent struct { +type AddProcessTask struct { s *Supervisor } // TODO: add this to worker for concurrent starts??? maybe not because of races where the container // could be stopped and removed... -func (h *AddProcessEvent) Handle(e *Event) error { +func (h *AddProcessTask) Handle(e *Task) error { start := time.Now() ci, ok := h.s.containers[e.ID] if !ok { return ErrContainerNotFound } - p, io, err := h.s.runtime.StartProcess(ci.container, *e.Process, e.Console) + process, err := ci.container.Exec(e.Pid, *e.ProcessSpec, runtime.NewStdio(e.Stdin, e.Stdout, e.Stderr)) if err != nil { return err } - if e.Pid, err = p.Pid(); err != nil { + if err := h.s.monitorProcess(process); err != nil { return err } - h.s.processes[e.Pid] = &containerInfo{ - container: ci.container, - } - l, err := h.s.copyIO(e.Stdin, e.Stdout, e.Stderr, io) - if err != nil { - // log the error but continue with the other commands - logrus.WithFields(logrus.Fields{ - "error": err, - "id": e.ID, - }).Error("log stdio") - } - h.s.processes[e.Pid].copier = l ExecProcessTimer.UpdateSince(start) + e.StartResponse <- StartResponse{} + h.s.notifySubscribers(Event{ + Timestamp: time.Now(), + Type: "start-process", + Pid: e.Pid, + ID: e.ID, + }) return nil } diff --git a/supervisor/checkpoint.go b/supervisor/checkpoint.go index a5ae540..53311eb 100644 --- a/supervisor/checkpoint.go +++ b/supervisor/checkpoint.go @@ -1,10 +1,10 @@ package supervisor -type CreateCheckpointEvent struct { +type CreateCheckpointTask struct { s *Supervisor } -func (h *CreateCheckpointEvent) Handle(e *Event) error { +func (h *CreateCheckpointTask) Handle(e *Task) error { i, ok := h.s.containers[e.ID] if !ok { return ErrContainerNotFound @@ -12,11 +12,11 @@ func (h *CreateCheckpointEvent) Handle(e *Event) error { return i.container.Checkpoint(*e.Checkpoint) } -type DeleteCheckpointEvent struct { +type DeleteCheckpointTask struct { s *Supervisor } -func (h *DeleteCheckpointEvent) Handle(e *Event) error { +func (h *DeleteCheckpointTask) Handle(e *Task) error { i, ok := h.s.containers[e.ID] if !ok { return ErrContainerNotFound diff --git a/supervisor/create.go b/supervisor/create.go index 41f4c8b..a65dea8 100644 --- a/supervisor/create.go +++ b/supervisor/create.go @@ -1,30 +1,32 @@ package supervisor -import "time" +import ( + "time" -type StartEvent struct { + "github.com/docker/containerd/runtime" +) + +type StartTask struct { s *Supervisor } -func (h *StartEvent) Handle(e *Event) error { +func (h *StartTask) Handle(e *Task) error { start := time.Now() - container, io, err := h.s.runtime.Create(e.ID, e.BundlePath, e.Console) + container, err := runtime.New(h.s.stateDir, e.ID, e.BundlePath, e.Labels) if err != nil { return err } - h.s.containerGroup.Add(1) h.s.containers[e.ID] = &containerInfo{ container: container, } ContainersCounter.Inc(1) - task := &StartTask{ + task := &startTask{ Err: e.Err, - IO: io, Container: container, + StartResponse: e.StartResponse, Stdin: e.Stdin, Stdout: e.Stdout, Stderr: e.Stderr, - StartResponse: e.StartResponse, } if e.Checkpoint != nil { task.Checkpoint = e.Checkpoint.Name diff --git a/supervisor/delete.go b/supervisor/delete.go index 4651ef8..1a11060 100644 --- a/supervisor/delete.go +++ b/supervisor/delete.go @@ -7,35 +7,30 @@ import ( "github.com/docker/containerd/runtime" ) -type DeleteEvent struct { +type DeleteTask struct { s *Supervisor } -func (h *DeleteEvent) Handle(e *Event) error { +func (h *DeleteTask) Handle(e *Task) error { if i, ok := h.s.containers[e.ID]; ok { start := time.Now() if err := h.deleteContainer(i.container); err != nil { logrus.WithField("error", err).Error("containerd: deleting container") } - if i.copier != nil { - if err := i.copier.Close(); err != nil { - logrus.WithField("error", err).Error("containerd: close container copier") - } - } - h.s.notifySubscribers(&Event{ - Type: ExitEventType, - ID: e.ID, - Status: e.Status, - Pid: e.Pid, + h.s.notifySubscribers(Event{ + Type: "exit", + Timestamp: time.Now(), + ID: e.ID, + Status: e.Status, + Pid: e.Pid, }) ContainersCounter.Dec(1) - h.s.containerGroup.Done() ContainerDeleteTimer.UpdateSince(start) } return nil } -func (h *DeleteEvent) deleteContainer(container runtime.Container) error { +func (h *DeleteTask) deleteContainer(container runtime.Container) error { delete(h.s.containers, container.ID()) return container.Delete() } diff --git a/supervisor/errors.go b/supervisor/errors.go index 9645ef1..7c8cf53 100644 --- a/supervisor/errors.go +++ b/supervisor/errors.go @@ -4,13 +4,13 @@ import "errors" var ( // External errors - ErrEventChanNil = errors.New("containerd: event channel is nil") + ErrTaskChanNil = errors.New("containerd: task channel is nil") ErrBundleNotFound = errors.New("containerd: bundle not found") ErrContainerNotFound = errors.New("containerd: container not found") ErrContainerExists = errors.New("containerd: container already exists") ErrProcessNotFound = errors.New("containerd: processs not found for container") ErrUnknownContainerStatus = errors.New("containerd: unknown container status ") - ErrUnknownEvent = errors.New("containerd: unknown event type") + ErrUnknownTask = errors.New("containerd: unknown task type") // Internal errors errShutdown = errors.New("containerd: supervisor is shutdown") diff --git a/supervisor/event.go b/supervisor/event.go deleted file mode 100644 index ac7ce00..0000000 --- a/supervisor/event.go +++ /dev/null @@ -1,84 +0,0 @@ -package supervisor - -import ( - "os" - "time" - - "github.com/docker/containerd/runtime" - "github.com/opencontainers/specs" -) - -type EventType string - -const ( - ExecExitEventType EventType = "execExit" - ExitEventType EventType = "exit" - StartContainerEventType EventType = "startContainer" - DeleteEventType EventType = "deleteContainerEvent" - GetContainerEventType EventType = "getContainer" - SignalEventType EventType = "signal" - AddProcessEventType EventType = "addProcess" - UpdateContainerEventType EventType = "updateContainer" - CreateCheckpointEventType EventType = "createCheckpoint" - DeleteCheckpointEventType EventType = "deleteCheckpoint" - StatsEventType EventType = "events" - UnsubscribeStatsEventType EventType = "unsubscribeStats" - StopStatsEventType EventType = "stopStats" - OOMEventType EventType = "oom" -) - -func NewEvent(t EventType) *Event { - return &Event{ - Type: t, - Timestamp: time.Now(), - Err: make(chan error, 1), - } -} - -type StartResponse struct { - Pid int -} - -type Event struct { - Type EventType - Timestamp time.Time - ID string - BundlePath string - Stdout string - Stderr string - Stdin string - Console string - Pid int - Status int - Signal os.Signal - Process *specs.Process - State runtime.State - Containers []runtime.Container - Checkpoint *runtime.Checkpoint - Err chan error - StartResponse chan StartResponse - Stats chan interface{} -} - -type Handler interface { - Handle(*Event) error -} - -type commonEvent struct { - data *Event - sv *Supervisor -} - -func (e *commonEvent) Handle() { - h, ok := e.sv.handlers[e.data.Type] - if !ok { - e.data.Err <- ErrUnknownEvent - return - } - err := h.Handle(e.data) - if err != errDeferedResponse { - e.data.Err <- err - close(e.data.Err) - return - } -} diff --git a/supervisor/exit.go b/supervisor/exit.go index 93a06c1..d0673ec 100644 --- a/supervisor/exit.go +++ b/supervisor/exit.go @@ -4,61 +4,62 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/containerd/runtime" ) -type ExitEvent struct { +type ExitTask struct { s *Supervisor } -func (h *ExitEvent) Handle(e *Event) error { +func (h *ExitTask) Handle(e *Task) error { start := time.Now() - logrus.WithFields(logrus.Fields{"pid": e.Pid, "status": e.Status}). - Debug("containerd: process exited") - // is it the child process of a container - if info, ok := h.s.processes[e.Pid]; ok { - ne := NewEvent(ExecExitEventType) - ne.ID = info.container.ID() - ne.Pid = e.Pid - ne.Status = e.Status - h.s.SendEvent(ne) - return nil - } - // is it the main container's process - container, err := h.s.getContainerForPid(e.Pid) + proc := e.Process + status, err := proc.ExitStatus() if err != nil { - if err != errNoContainerForPid { - logrus.WithField("error", err).Error("containerd: find containers main pid") - } + logrus.WithField("error", err).Error("containerd: get exit status") + } + logrus.WithFields(logrus.Fields{"pid": proc.ID(), "status": status}).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 := NewTask(ExecExitTaskType) + ne.ID = proc.Container().ID() + ne.Pid = proc.ID() + ne.Status = status + ne.Process = proc + h.s.SendTask(ne) + return nil } - container.SetExited(e.Status) - ne := NewEvent(DeleteEventType) + container := proc.Container() + ne := NewTask(DeleteTaskType) ne.ID = container.ID() - ne.Pid = e.Pid - ne.Status = e.Status - h.s.SendEvent(ne) + ne.Status = status + ne.Pid = proc.ID() + h.s.SendTask(ne) - stopCollect := NewEvent(StopStatsEventType) - stopCollect.ID = container.ID() - h.s.SendEvent(stopCollect) ExitProcessTimer.UpdateSince(start) + return nil } -type ExecExitEvent struct { +type ExecExitTask struct { s *Supervisor } -func (h *ExecExitEvent) Handle(e *Event) error { +func (h *ExecExitTask) Handle(e *Task) error { + container := e.Process.Container() // exec process: we remove this process without notifying the main event loop - info := h.s.processes[e.Pid] - if err := info.container.RemoveProcess(e.Pid); err != nil { + if err := container.RemoveProcess(e.Pid); err != nil { logrus.WithField("error", err).Error("containerd: find container for pid") } - if err := info.copier.Close(); err != nil { - logrus.WithField("error", err).Error("containerd: close process IO") - } - delete(h.s.processes, e.Pid) - h.s.notifySubscribers(e) + h.s.notifySubscribers(Event{ + Timestamp: time.Now(), + ID: e.ID, + Type: "exit", + Pid: e.Pid, + Status: e.Status, + }) return nil } diff --git a/supervisor/get_containers.go b/supervisor/get_containers.go index 0b21e36..64acc3f 100644 --- a/supervisor/get_containers.go +++ b/supervisor/get_containers.go @@ -1,10 +1,18 @@ package supervisor -type GetContainersEvent struct { +type GetContainersTask struct { s *Supervisor } -func (h *GetContainersEvent) Handle(e *Event) error { +func (h *GetContainersTask) Handle(e *Task) error { + if e.ID != "" { + ci := h.s.containers[e.ID] + if ci == nil { + return ErrContainerNotFound + } + e.Containers = append(e.Containers, ci.container) + return nil + } for _, i := range h.s.containers { e.Containers = append(e.Containers, i.container) } diff --git a/supervisor/io.go b/supervisor/io.go deleted file mode 100644 index fb43f07..0000000 --- a/supervisor/io.go +++ /dev/null @@ -1,66 +0,0 @@ -package supervisor - -import ( - "io" - "os" -) - -type ioConfig struct { - StdoutPath string - StderrPath string - StdinPath string - - Stdin io.WriteCloser - Stdout io.ReadCloser - Stderr io.ReadCloser -} - -func newCopier(i *ioConfig) (*copier, error) { - l := &copier{ - config: i, - } - if i.StdinPath != "" { - f, err := os.OpenFile(i.StdinPath, os.O_RDONLY, 0) - if err != nil { - return nil, err - } - l.closers = append(l.closers, f) - go func() { - io.Copy(i.Stdin, f) - i.Stdin.Close() - }() - } - if i.StdoutPath != "" { - f, err := os.OpenFile(i.StdoutPath, os.O_RDWR, 0) - if err != nil { - return nil, err - } - l.closers = append(l.closers, f) - go io.Copy(f, i.Stdout) - } - if i.StderrPath != "" { - f, err := os.OpenFile(i.StderrPath, os.O_RDWR, 0) - if err != nil { - return nil, err - } - l.closers = append(l.closers, f) - go io.Copy(f, i.Stderr) - } - return l, nil -} - -type copier struct { - config *ioConfig - closers []io.Closer -} - -func (l *copier) Close() (err error) { - for _, c := range append(l.closers, l.config.Stdin, l.config.Stdout, l.config.Stderr) { - if c != nil { - if cerr := c.Close(); err == nil { - err = cerr - } - } - } - return err -} diff --git a/supervisor/machine.go b/supervisor/machine.go index e25932a..1dcada5 100644 --- a/supervisor/machine.go +++ b/supervisor/machine.go @@ -3,15 +3,12 @@ package supervisor import "github.com/cloudfoundry/gosigar" type Machine struct { - ID string Cpus int Memory int64 } -func CollectMachineInformation(id string) (Machine, error) { - m := Machine{ - ID: id, - } +func CollectMachineInformation() (Machine, error) { + m := Machine{} cpu := sigar.CpuList{} if err := cpu.Get(); err != nil { return m, err @@ -21,6 +18,6 @@ func CollectMachineInformation(id string) (Machine, error) { if err := mem.Get(); err != nil { return m, err } - m.Memory = int64(mem.Total) + m.Memory = int64(mem.Total / 1024 / 1024) return m, nil } diff --git a/supervisor/metrics.go b/supervisor/metrics.go index 80ca4ba..2ba772a 100644 --- a/supervisor/metrics.go +++ b/supervisor/metrics.go @@ -6,11 +6,13 @@ var ( ContainerCreateTimer = metrics.NewTimer() ContainerDeleteTimer = metrics.NewTimer() ContainerStartTimer = metrics.NewTimer() + ContainerStatsTimer = metrics.NewTimer() ContainersCounter = metrics.NewCounter() EventSubscriberCounter = metrics.NewCounter() - EventsCounter = metrics.NewCounter() + TasksCounter = metrics.NewCounter() ExecProcessTimer = metrics.NewTimer() ExitProcessTimer = metrics.NewTimer() + EpollFdCounter = metrics.NewCounter() ) func Metrics() map[string]interface{} { @@ -18,10 +20,12 @@ func Metrics() map[string]interface{} { "container-create-time": ContainerCreateTimer, "container-delete-time": ContainerDeleteTimer, "container-start-time": ContainerStartTimer, + "container-stats-time": ContainerStatsTimer, "containers": ContainersCounter, "event-subscribers": EventSubscriberCounter, - "events": EventsCounter, + "tasks": TasksCounter, "exec-process-time": ExecProcessTimer, "exit-process-time": ExitProcessTimer, + "epoll-fds": EpollFdCounter, } } diff --git a/supervisor/monitor.go b/supervisor/monitor.go new file mode 100644 index 0000000..3a3046b --- /dev/null +++ b/supervisor/monitor.go @@ -0,0 +1,88 @@ +package supervisor + +import ( + "sync" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/docker/containerd/runtime" +) + +func NewMonitor() (*Monitor, error) { + m := &Monitor{ + processes: make(map[int]runtime.Process), + exits: make(chan runtime.Process, 1024), + } + fd, err := syscall.EpollCreate1(0) + if err != nil { + return nil, err + } + m.epollFd = fd + go m.start() + return m, nil +} + +type Monitor struct { + m sync.Mutex + processes map[int]runtime.Process + exits chan runtime.Process + epollFd int +} + +func (m *Monitor) Exits() chan runtime.Process { + return m.exits +} + +func (m *Monitor) Monitor(p runtime.Process) error { + m.m.Lock() + defer m.m.Unlock() + fd := p.ExitFD() + event := syscall.EpollEvent{ + Fd: int32(fd), + Events: syscall.EPOLLHUP, + } + if err := syscall.EpollCtl(m.epollFd, syscall.EPOLL_CTL_ADD, fd, &event); err != nil { + return err + } + EpollFdCounter.Inc(1) + m.processes[fd] = p + return nil +} + +func (m *Monitor) Close() error { + return syscall.Close(m.epollFd) +} + +func (m *Monitor) start() { + var events [128]syscall.EpollEvent + for { + n, err := syscall.EpollWait(m.epollFd, events[:], -1) + if err != nil { + if err == syscall.EINTR { + continue + } + logrus.WithField("error", err).Fatal("containerd: epoll wait") + } + // process events + for i := 0; i < n; i++ { + if events[i].Events == syscall.EPOLLHUP { + fd := int(events[i].Fd) + m.m.Lock() + proc := m.processes[fd] + delete(m.processes, fd) + if err = syscall.EpollCtl(m.epollFd, syscall.EPOLL_CTL_DEL, fd, &syscall.EpollEvent{ + Events: syscall.EPOLLHUP, + Fd: int32(fd), + }); err != nil { + logrus.WithField("error", err).Fatal("containerd: epoll remove fd") + } + EpollFdCounter.Dec(1) + if err := proc.Close(); err != nil { + logrus.WithField("error", err).Error("containerd: close process IO") + } + m.m.Unlock() + m.exits <- proc + } + } + } +} diff --git a/supervisor/signal.go b/supervisor/signal.go index 7d0fcab..c88a916 100644 --- a/supervisor/signal.go +++ b/supervisor/signal.go @@ -1,10 +1,10 @@ package supervisor -type SignalEvent struct { +type SignalTask struct { s *Supervisor } -func (h *SignalEvent) Handle(e *Event) error { +func (h *SignalTask) Handle(e *Task) error { i, ok := h.s.containers[e.ID] if !ok { return ErrContainerNotFound @@ -14,7 +14,7 @@ func (h *SignalEvent) Handle(e *Event) error { return err } for _, p := range processes { - if pid, err := p.Pid(); err == nil && pid == e.Pid { + if p.ID() == e.Pid { return p.Signal(e.Signal) } } diff --git a/supervisor/sort_test.go b/supervisor/sort_test.go new file mode 100644 index 0000000..9202ac3 --- /dev/null +++ b/supervisor/sort_test.go @@ -0,0 +1,73 @@ +package supervisor + +import ( + "os" + "sort" + "testing" + + "github.com/docker/containerd/runtime" + "github.com/opencontainers/specs" +) + +type testProcess struct { + id string +} + +func (p *testProcess) ID() string { + return p.id +} + +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() (int, error) { + return -1, nil +} + +func (p *testProcess) Container() runtime.Container { + return nil +} + +func (p *testProcess) Spec() specs.Process { + return specs.Process{} +} + +func (p *testProcess) Signal(os.Signal) error { + return nil +} + +func (p *testProcess) Close() error { + return nil +} + +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) + } +} diff --git a/supervisor/stats.go b/supervisor/stats.go index 7f3a9ec..6cbbfed 100644 --- a/supervisor/stats.go +++ b/supervisor/stats.go @@ -1,40 +1,27 @@ package supervisor -type StatsEvent struct { +import "time" + +type StatsTask struct { s *Supervisor } -type UnsubscribeStatsEvent struct { - s *Supervisor -} - -type StopStatsEvent struct { - s *Supervisor -} - -func (h *StatsEvent) Handle(e *Event) error { +func (h *StatsTask) Handle(e *Task) error { + start := time.Now() i, ok := h.s.containers[e.ID] if !ok { return ErrContainerNotFound } - e.Stats = h.s.statsCollector.collect(i.container) - return nil -} - -func (h *UnsubscribeStatsEvent) Handle(e *Event) error { - i, ok := h.s.containers[e.ID] - if !ok { - return ErrContainerNotFound - } - h.s.statsCollector.unsubscribe(i.container, e.Stats) - return nil -} - -func (h *StopStatsEvent) Handle(e *Event) error { - i, ok := h.s.containers[e.ID] - if !ok { - return ErrContainerNotFound - } - h.s.statsCollector.stopCollection(i.container) - return nil + // TODO: use workers for this + go func() { + s, err := i.container.Stats() + if err != nil { + e.Err <- err + return + } + e.Err <- nil + e.Stat <- s + ContainerStatsTimer.UpdateSince(start) + }() + return errDeferedResponse } diff --git a/supervisor/stats_collector.go b/supervisor/stats_collector.go deleted file mode 100644 index f45b4ae..0000000 --- a/supervisor/stats_collector.go +++ /dev/null @@ -1,240 +0,0 @@ -package supervisor - -import ( - "bufio" - "fmt" - "os" - "strconv" - "strings" - "sync" - "time" - - "github.com/Sirupsen/logrus" - "github.com/docker/containerd/api/grpc/types" - "github.com/docker/containerd/runtime" - "github.com/docker/docker/pkg/pubsub" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/cgroups" - "github.com/opencontainers/runc/libcontainer/system" -) - -func convertBlkioEntryToPb(b []cgroups.BlkioStatEntry) []*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 convertToPb(st *runtime.Stat) *types.Stats { - pbSt := &types.Stats{ - Timestamp: uint64(st.Timestamp.Unix()), - CgroupStats: &types.CgroupStats{}, - } - lcSt, ok := st.Data.(*libcontainer.Stats) - if !ok { - return pbSt - } - cpuSt := lcSt.CgroupStats.CpuStats - pbSt.CgroupStats.CpuStats = &types.CpuStats{ - CpuUsage: &types.CpuUsage{ - TotalUsage: cpuSt.CpuUsage.TotalUsage, - PercpuUsage: cpuSt.CpuUsage.PercpuUsage, - UsageInKernelmode: cpuSt.CpuUsage.UsageInKernelmode, - UsageInUsermode: cpuSt.CpuUsage.UsageInUsermode, - }, - ThrottlingData: &types.ThrottlingData{ - Periods: cpuSt.ThrottlingData.Periods, - ThrottledPeriods: cpuSt.ThrottlingData.ThrottledPeriods, - ThrottledTime: cpuSt.ThrottlingData.ThrottledTime, - }, - } - memSt := lcSt.CgroupStats.MemoryStats - pbSt.CgroupStats.MemoryStats = &types.MemoryStats{ - Cache: memSt.Cache, - Usage: &types.MemoryData{ - Usage: memSt.Usage.Usage, - MaxUsage: memSt.Usage.MaxUsage, - Failcnt: memSt.Usage.Failcnt, - }, - SwapUsage: &types.MemoryData{ - Usage: memSt.SwapUsage.Usage, - MaxUsage: memSt.SwapUsage.MaxUsage, - Failcnt: memSt.SwapUsage.Failcnt, - }, - } - blkSt := lcSt.CgroupStats.BlkioStats - pbSt.CgroupStats.BlkioStats = &types.BlkioStats{ - IoServiceBytesRecursive: convertBlkioEntryToPb(blkSt.IoServiceBytesRecursive), - IoServicedRecursive: convertBlkioEntryToPb(blkSt.IoServicedRecursive), - IoQueuedRecursive: convertBlkioEntryToPb(blkSt.IoQueuedRecursive), - IoServiceTimeRecursive: convertBlkioEntryToPb(blkSt.IoServiceTimeRecursive), - IoWaitTimeRecursive: convertBlkioEntryToPb(blkSt.IoWaitTimeRecursive), - IoMergedRecursive: convertBlkioEntryToPb(blkSt.IoMergedRecursive), - IoTimeRecursive: convertBlkioEntryToPb(blkSt.IoTimeRecursive), - SectorsRecursive: convertBlkioEntryToPb(blkSt.SectorsRecursive), - } - pbSt.CgroupStats.HugetlbStats = make(map[string]*types.HugetlbStats) - for k, st := range lcSt.CgroupStats.HugetlbStats { - pbSt.CgroupStats.HugetlbStats[k] = &types.HugetlbStats{ - Usage: st.Usage, - MaxUsage: st.MaxUsage, - Failcnt: st.Failcnt, - } - } - return pbSt -} - -type statsPair struct { - ct runtime.Container - pub *pubsub.Publisher -} - -func newStatsCollector(interval time.Duration) *statsCollector { - s := &statsCollector{ - interval: interval, - clockTicksPerSecond: uint64(system.GetClockTicks()), - bufReader: bufio.NewReaderSize(nil, 128), - publishers: make(map[string]*statsPair), - } - go s.run() - return s -} - -// statsCollector manages and provides container resource stats -type statsCollector struct { - m sync.Mutex - supervisor *Supervisor - interval time.Duration - clockTicksPerSecond uint64 - publishers map[string]*statsPair - bufReader *bufio.Reader -} - -// collect registers the container with the collector and adds it to -// the event loop for collection on the specified interval returning -// a channel for the subscriber to receive on. -func (s *statsCollector) collect(c runtime.Container) chan interface{} { - s.m.Lock() - defer s.m.Unlock() - publisher, exists := s.publishers[c.ID()] - if !exists { - pub := pubsub.NewPublisher(100*time.Millisecond, 1024) - publisher = &statsPair{ct: c, pub: pub} - s.publishers[c.ID()] = publisher - } - return publisher.pub.Subscribe() -} - -// stopCollection closes the channels for all subscribers and removes -// the container from metrics collection. -func (s *statsCollector) stopCollection(c runtime.Container) { - s.m.Lock() - if publisher, exists := s.publishers[c.ID()]; exists { - publisher.pub.Close() - delete(s.publishers, c.ID()) - } - s.m.Unlock() -} - -// unsubscribe removes a specific subscriber from receiving updates for a container's stats. -func (s *statsCollector) unsubscribe(c runtime.Container, ch chan interface{}) { - s.m.Lock() - publisher := s.publishers[c.ID()] - if publisher != nil { - publisher.pub.Evict(ch) - if publisher.pub.Len() == 0 { - delete(s.publishers, c.ID()) - } - } - s.m.Unlock() -} - -func (s *statsCollector) run() { - type publishersPair struct { - container runtime.Container - publisher *pubsub.Publisher - } - // we cannot determine the capacity here. - // it will grow enough in first iteration - var pairs []*statsPair - - for range time.Tick(s.interval) { - // it does not make sense in the first iteration, - // but saves allocations in further iterations - pairs = pairs[:0] - - s.m.Lock() - for _, publisher := range s.publishers { - // copy pointers here to release the lock ASAP - pairs = append(pairs, publisher) - } - s.m.Unlock() - if len(pairs) == 0 { - continue - } - - for _, pair := range pairs { - stats, err := pair.ct.Stats() - if err != nil { - logrus.Errorf("Error getting stats for container ID %s", pair.ct.ID()) - continue - } - - pair.pub.Publish(convertToPb(stats)) - } - } -} - -const nanoSecondsPerSecond = 1e9 - -// getSystemCPUUsage returns the host system's cpu usage in -// nanoseconds. An error is returned if the format of the underlying -// file does not match. -// -// Uses /proc/stat defined by POSIX. Looks for the cpu -// statistics line and then sums up the first seven fields -// provided. See `man 5 proc` for details on specific field -// information. -func (s *statsCollector) getSystemCPUUsage() (uint64, error) { - var line string - f, err := os.Open("/proc/stat") - if err != nil { - return 0, err - } - defer func() { - s.bufReader.Reset(nil) - f.Close() - }() - s.bufReader.Reset(f) - err = nil - for err == nil { - line, err = s.bufReader.ReadString('\n') - if err != nil { - break - } - parts := strings.Fields(line) - switch parts[0] { - case "cpu": - if len(parts) < 8 { - return 0, fmt.Errorf("bad format of cpu stats") - } - var totalClockTicks uint64 - for _, i := range parts[1:8] { - v, err := strconv.ParseUint(i, 10, 64) - if err != nil { - return 0, fmt.Errorf("error parsing cpu stats") - } - totalClockTicks += v - } - return (totalClockTicks * nanoSecondsPerSecond) / - s.clockTicksPerSecond, nil - } - } - return 0, fmt.Errorf("bad stats format") -} diff --git a/supervisor/supervisor.go b/supervisor/supervisor.go index 5c54a44..75c22b7 100644 --- a/supervisor/supervisor.go +++ b/supervisor/supervisor.go @@ -1,144 +1,156 @@ package supervisor import ( + "encoding/json" + "io" + "io/ioutil" "os" - "os/signal" "path/filepath" + "sort" "sync" - "syscall" "time" "github.com/Sirupsen/logrus" "github.com/docker/containerd/chanotify" "github.com/docker/containerd/eventloop" "github.com/docker/containerd/runtime" - "github.com/opencontainers/runc/libcontainer" ) const ( - statsInterval = 1 * time.Second defaultBufferSize = 2048 // size of queue in eventloop ) // New returns an initialized Process supervisor. -func New(id, stateDir string, tasks chan *StartTask, oom bool) (*Supervisor, error) { +func New(stateDir string, oom bool) (*Supervisor, error) { + tasks := make(chan *startTask, 10) if err := os.MkdirAll(stateDir, 0755); err != nil { return nil, err } - // register counters - r, err := newRuntime(filepath.Join(stateDir, id)) + machine, err := CollectMachineInformation() if err != nil { return nil, err } - machine, err := CollectMachineInformation(id) + monitor, err := NewMonitor() if err != nil { return nil, err } s := &Supervisor{ - stateDir: stateDir, - containers: make(map[string]*containerInfo), - processes: make(map[int]*containerInfo), - runtime: r, - tasks: tasks, - machine: machine, - subscribers: make(map[chan *Event]struct{}), - statsCollector: newStatsCollector(statsInterval), - el: eventloop.NewChanLoop(defaultBufferSize), + stateDir: stateDir, + containers: make(map[string]*containerInfo), + tasks: tasks, + machine: machine, + subscribers: make(map[chan Event]struct{}), + el: eventloop.NewChanLoop(defaultBufferSize), + monitor: monitor, + } + if err := setupEventLog(s); err != nil { + return nil, err } if oom { s.notifier = chanotify.New() go func() { for id := range s.notifier.Chan() { - e := NewEvent(OOMEventType) + e := NewTask(OOMTaskType) e.ID = id.(string) - s.SendEvent(e) + s.SendTask(e) } }() } // register default event handlers - s.handlers = map[EventType]Handler{ - ExecExitEventType: &ExecExitEvent{s}, - ExitEventType: &ExitEvent{s}, - StartContainerEventType: &StartEvent{s}, - DeleteEventType: &DeleteEvent{s}, - GetContainerEventType: &GetContainersEvent{s}, - SignalEventType: &SignalEvent{s}, - AddProcessEventType: &AddProcessEvent{s}, - UpdateContainerEventType: &UpdateEvent{s}, - CreateCheckpointEventType: &CreateCheckpointEvent{s}, - DeleteCheckpointEventType: &DeleteCheckpointEvent{s}, - StatsEventType: &StatsEvent{s}, - UnsubscribeStatsEventType: &UnsubscribeStatsEvent{s}, - StopStatsEventType: &StopStatsEvent{s}, + s.handlers = map[TaskType]Handler{ + ExecExitTaskType: &ExecExitTask{s}, + ExitTaskType: &ExitTask{s}, + StartContainerTaskType: &StartTask{s}, + DeleteTaskType: &DeleteTask{s}, + GetContainerTaskType: &GetContainersTask{s}, + SignalTaskType: &SignalTask{s}, + AddProcessTaskType: &AddProcessTask{s}, + UpdateContainerTaskType: &UpdateTask{s}, + CreateCheckpointTaskType: &CreateCheckpointTask{s}, + DeleteCheckpointTaskType: &DeleteCheckpointTask{s}, + StatsTaskType: &StatsTask{s}, + UpdateProcessTaskType: &UpdateProcessTask{s}, + } + go s.exitHandler() + if err := s.restore(); err != nil { + return nil, err } - // start the container workers for concurrent container starts return s, nil } type containerInfo struct { container runtime.Container - copier *copier +} + +func setupEventLog(s *Supervisor) 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{}) + f, err := os.OpenFile(filepath.Join(s.stateDir, "events.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755) + if err != nil { + return err + } + enc := json.NewEncoder(f) + go func() { + for e := range events { + s.eventLog = append(s.eventLog, e) + 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.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 { + return nil + } + return err + } + s.eventLog = append(s.eventLog, e) + } + return nil } type Supervisor struct { // stateDir is the directory on the system to store container runtime state information. stateDir string containers map[string]*containerInfo - processes map[int]*containerInfo - handlers map[EventType]Handler - runtime runtime.Runtime - events chan *Event - tasks chan *StartTask + handlers map[TaskType]Handler + events chan *Task + tasks 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{} + subscribers map[chan Event]struct{} machine Machine - containerGroup sync.WaitGroup - statsCollector *statsCollector notifier *chanotify.Notifier el eventloop.EventLoop + monitor *Monitor + eventLog []Event } // Stop closes all tasks 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(sig chan os.Signal) { +func (s *Supervisor) Stop() { // Close the tasks channel so that no new containers get started close(s.tasks) - // send a SIGTERM to all containers - for id, i := range s.containers { - c := i.container - logrus.WithField("id", id).Debug("sending TERM to container processes") - procs, err := c.Processes() - if err != nil { - logrus.WithField("id", id).Warn("get container processes") - continue - } - if len(procs) == 0 { - continue - } - mainProc := procs[0] - if err := mainProc.Signal(syscall.SIGTERM); err != nil { - pid, _ := mainProc.Pid() - logrus.WithFields(logrus.Fields{ - "id": id, - "pid": pid, - "error": err, - }).Error("send SIGTERM to process") - } - } - go func() { - logrus.Debug("waiting for containers to exit") - s.containerGroup.Wait() - logrus.Debug("all containers exited") - if s.notifier != nil { - s.notifier.Close() - } - // stop receiving signals and close the channel - signal.Stop(sig) - close(sig) - }() } // Close closes any open files in the supervisor but expects that Stop has been @@ -147,19 +159,35 @@ func (s *Supervisor) Close() error { return nil } +type Event struct { + ID string `json:"id"` + Type string `json:"type"` + Timestamp time.Time `json:"timestamp"` + Pid string `json:"pid,omitempty"` + Status int `json:"status,omitempty"` +} + // Events returns an event channel that external consumers can use to receive updates // on container events -func (s *Supervisor) Events() chan *Event { +func (s *Supervisor) Events(from time.Time) chan Event { s.subscriberLock.Lock() defer s.subscriberLock.Unlock() - c := make(chan *Event, defaultBufferSize) + c := make(chan Event, defaultBufferSize) EventSubscriberCounter.Inc(1) s.subscribers[c] = struct{}{} + if !from.IsZero() { + // replay old event + for _, e := range s.eventLog { + if e.Timestamp.After(from) { + c <- e + } + } + } return c } // Unsubscribe removes the provided channel from receiving any more events -func (s *Supervisor) Unsubscribe(sub chan *Event) { +func (s *Supervisor) Unsubscribe(sub chan Event) { s.subscriberLock.Lock() defer s.subscriberLock.Unlock() delete(s.subscribers, sub) @@ -169,7 +197,7 @@ func (s *Supervisor) Unsubscribe(sub chan *Event) { // notifySubscribers will send the provided event to the external subscribers // of the events channel -func (s *Supervisor) notifySubscribers(e *Event) { +func (s *Supervisor) notifySubscribers(e Event) { s.subscriberLock.RLock() defer s.subscriberLock.RUnlock() for sub := range s.subscribers { @@ -177,7 +205,7 @@ func (s *Supervisor) notifySubscribers(e *Event) { select { case sub <- e: default: - logrus.WithField("event", e.Type).Warn("event not sent to subscriber") + logrus.WithField("event", e.Type).Warn("containerd: event not sent to subscriber") } } } @@ -189,10 +217,7 @@ func (s *Supervisor) notifySubscribers(e *Event) { // 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{ - "runtime": s.runtime.Type(), - "stateDir": s.stateDir, - }).Debug("Supervisor started") + logrus.WithField("stateDir", s.stateDir).Debug("containerd: supervisor running") return s.el.Start() } @@ -202,45 +227,83 @@ func (s *Supervisor) Machine() Machine { return s.machine } -// getContainerForPid returns the container where the provided pid is the pid1 or main -// process in the container -func (s *Supervisor) getContainerForPid(pid int) (runtime.Container, error) { - for _, i := range s.containers { - container := i.container - cpid, err := container.Pid() +// SendTask sends the provided event the the supervisors main event loop +func (s *Supervisor) SendTask(evt *Task) { + TasksCounter.Inc(1) + s.el.Send(&commonTask{data: evt, sv: s}) +} + +func (s *Supervisor) exitHandler() { + for p := range s.monitor.Exits() { + e := NewTask(ExitTaskType) + e.Process = p + s.SendTask(e) + } +} + +func (s *Supervisor) monitorProcess(p runtime.Process) error { + return s.monitor.Monitor(p) +} + +func (s *Supervisor) restore() error { + dirs, err := ioutil.ReadDir(s.stateDir) + if err != nil { + return err + } + for _, d := range dirs { + if !d.IsDir() { + continue + } + id := d.Name() + container, err := runtime.Load(s.stateDir, id) if err != nil { - if lerr, ok := err.(libcontainer.Error); ok { - if lerr.Code() == libcontainer.ProcessNotExecuted { - continue + return err + } + processes, err := container.Processes() + if err != nil { + return err + } + ContainersCounter.Inc(1) + s.containers[id] = &containerInfo{ + container: container, + } + logrus.WithField("id", id).Debug("containerd: container restored") + var exitedProcesses []runtime.Process + for _, p := range processes { + if _, err := p.ExitStatus(); err == nil { + exitedProcesses = append(exitedProcesses, p) + } else { + if err := s.monitorProcess(p); err != nil { + return err } } - logrus.WithField("error", err).Error("containerd: get container pid") } - if pid == cpid { - return container, nil + if len(exitedProcesses) > 0 { + // sort processes so that init is fired last because that is how the kernel sends the + // exit events + sort.Sort(&processSorter{exitedProcesses}) + for _, p := range exitedProcesses { + e := NewTask(ExitTaskType) + e.Process = p + s.SendTask(e) + } } } - return nil, errNoContainerForPid + return nil } -// SendEvent sends the provided event the the supervisors main event loop -func (s *Supervisor) SendEvent(evt *Event) { - EventsCounter.Inc(1) - s.el.Send(&commonEvent{data: evt, sv: s}) +type processSorter struct { + processes []runtime.Process } -func (s *Supervisor) copyIO(stdin, stdout, stderr string, i *runtime.IO) (*copier, error) { - config := &ioConfig{ - Stdin: i.Stdin, - Stdout: i.Stdout, - Stderr: i.Stderr, - StdoutPath: stdout, - StderrPath: stderr, - StdinPath: stdin, - } - l, err := newCopier(config) - if err != nil { - return nil, err - } - return l, nil +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" } diff --git a/supervisor/supervisor_linux.go b/supervisor/supervisor_linux.go deleted file mode 100644 index ec75542..0000000 --- a/supervisor/supervisor_linux.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build libcontainer - -package supervisor - -import ( - "github.com/docker/containerd/linux" - "github.com/docker/containerd/runtime" -) - -func newRuntime(stateDir string) (runtime.Runtime, error) { - return linux.NewRuntime(stateDir) -} diff --git a/supervisor/supervisor_runc.go b/supervisor/supervisor_runc.go deleted file mode 100644 index a0b7b49..0000000 --- a/supervisor/supervisor_runc.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build runc - -package supervisor - -import ( - "github.com/docker/containerd/runc" - "github.com/docker/containerd/runtime" -) - -func newRuntime(stateDir string) (runtime.Runtime, error) { - return runc.NewRuntime(stateDir) -} diff --git a/supervisor/supervisor_unsupported.go b/supervisor/supervisor_unsupported.go deleted file mode 100644 index 2b54893..0000000 --- a/supervisor/supervisor_unsupported.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build !libcontainer,!runc - -package supervisor - -import ( - "errors" - - "github.com/docker/containerd/runtime" -) - -func newRuntime(stateDir string) (runtime.Runtime, error) { - return nil, errors.New("unsupported platform") -} diff --git a/supervisor/task.go b/supervisor/task.go new file mode 100644 index 0000000..2844e77 --- /dev/null +++ b/supervisor/task.go @@ -0,0 +1,89 @@ +package supervisor + +import ( + "os" + "time" + + "github.com/docker/containerd/runtime" + "github.com/opencontainers/specs" +) + +type TaskType string + +const ( + ExecExitTaskType TaskType = "execExit" + ExitTaskType TaskType = "exit" + StartContainerTaskType TaskType = "startContainer" + DeleteTaskType TaskType = "deleteContainerEvent" + GetContainerTaskType TaskType = "getContainer" + SignalTaskType TaskType = "signal" + AddProcessTaskType TaskType = "addProcess" + UpdateContainerTaskType TaskType = "updateContainer" + UpdateProcessTaskType TaskType = "updateProcess" + CreateCheckpointTaskType TaskType = "createCheckpoint" + DeleteCheckpointTaskType TaskType = "deleteCheckpoint" + StatsTaskType TaskType = "events" + OOMTaskType TaskType = "oom" +) + +func NewTask(t TaskType) *Task { + return &Task{ + Type: t, + Timestamp: time.Now(), + Err: make(chan error, 1), + } +} + +type StartResponse struct { + Container runtime.Container +} + +type Task struct { + Type TaskType + Timestamp time.Time + ID string + BundlePath string + Stdout string + Stderr string + Stdin string + Console string + Pid string + Status int + Signal os.Signal + Process runtime.Process + State runtime.State + ProcessSpec *specs.Process + Containers []runtime.Container + Checkpoint *runtime.Checkpoint + Err chan error + StartResponse chan StartResponse + Stat chan *runtime.Stat + CloseStdin bool + ResizeTty bool + Width int + Height int + Labels []string +} + +type Handler interface { + Handle(*Task) error +} + +type commonTask struct { + data *Task + sv *Supervisor +} + +func (e *commonTask) Handle() { + h, ok := e.sv.handlers[e.data.Type] + if !ok { + e.data.Err <- ErrUnknownTask + return + } + err := h.Handle(e.data) + if err != errDeferedResponse { + e.data.Err <- err + close(e.data.Err) + return + } +} diff --git a/supervisor/update.go b/supervisor/update.go index 5bd4ffa..f4a178e 100644 --- a/supervisor/update.go +++ b/supervisor/update.go @@ -1,12 +1,16 @@ package supervisor -import "github.com/docker/containerd/runtime" +import ( + "time" -type UpdateEvent struct { + "github.com/docker/containerd/runtime" +) + +type UpdateTask struct { s *Supervisor } -func (h *UpdateEvent) Handle(e *Event) error { +func (h *UpdateTask) Handle(e *Task) error { i, ok := h.s.containers[e.ID] if !ok { return ErrContainerNotFound @@ -18,24 +22,59 @@ func (h *UpdateEvent) Handle(e *Event) error { if err := container.Resume(); err != nil { return ErrUnknownContainerStatus } + h.s.notifySubscribers(Event{ + ID: e.ID, + Type: "resume", + Timestamp: time.Now(), + }) case runtime.Paused: if err := container.Pause(); err != nil { return ErrUnknownContainerStatus } + h.s.notifySubscribers(Event{ + ID: e.ID, + Type: "pause", + Timestamp: time.Now(), + }) default: return ErrUnknownContainerStatus } } - if e.Signal != nil { - // signal the pid1/main process of the container - processes, err := container.Processes() - if err != nil { + return nil +} + +type UpdateProcessTask struct { + s *Supervisor +} + +func (h *UpdateProcessTask) Handle(e *Task) error { + i, ok := h.s.containers[e.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() == e.Pid { + process = p + break + } + } + if process == nil { + return ErrProcessNotFound + } + if e.CloseStdin { + if err := process.CloseStdin(); err != nil { return err } - if len(processes) == 0 { - return ErrProcessNotFound + } + if e.Width > 0 || e.Height > 0 { + if err := process.Resize(e.Width, e.Height); err != nil { + return err } - return processes[0].Signal(e.Signal) } return nil } diff --git a/supervisor/worker.go b/supervisor/worker.go index 4acb8d2..a481c73 100644 --- a/supervisor/worker.go +++ b/supervisor/worker.go @@ -12,10 +12,9 @@ type Worker interface { Start() } -type StartTask struct { +type startTask struct { Container runtime.Container Checkpoint string - IO *runtime.IO Stdin string Stdout string Stderr string @@ -39,48 +38,36 @@ func (w *worker) Start() { defer w.wg.Done() for t := range w.s.tasks { started := time.Now() - l, err := w.s.copyIO(t.Stdin, t.Stdout, t.Stderr, t.IO) + process, err := t.Container.Start(t.Checkpoint, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr)) if err != nil { - evt := NewEvent(DeleteEventType) + evt := NewTask(DeleteTaskType) evt.ID = t.Container.ID() - w.s.SendEvent(evt) + w.s.SendTask(evt) t.Err <- err continue } - w.s.containers[t.Container.ID()].copier = l - if t.Checkpoint != "" { - if err := t.Container.Restore(t.Checkpoint); err != nil { - evt := NewEvent(DeleteEventType) - evt.ID = t.Container.ID() - w.s.SendEvent(evt) - t.Err <- err - continue - } - } else { - if err := t.Container.Start(); err != nil { - evt := NewEvent(DeleteEventType) - evt.ID = t.Container.ID() - w.s.SendEvent(evt) - t.Err <- err - continue - } - } - pid, err := t.Container.Pid() - if err != nil { - logrus.WithField("error", err).Error("containerd: get container main pid") - } - if w.s.notifier != nil { - n, err := t.Container.OOM() - if err != nil { - logrus.WithField("error", err).Error("containerd: notify OOM events") - } else { - w.s.notifier.Add(t.Container.ID(), n) - } + /* + if w.s.notifier != nil { + n, err := t.Container.OOM() + if err != nil { + logrus.WithField("error", err).Error("containerd: notify OOM events") + } else { + w.s.notifier.Add(n, t.Container.ID()) + } + } + */ + if err := w.s.monitorProcess(process); err != nil { + logrus.WithField("error", err).Error("containerd: add process to monitor") } ContainerStartTimer.UpdateSince(started) t.Err <- nil t.StartResponse <- StartResponse{ - Pid: pid, + Container: t.Container, } + w.s.notifySubscribers(Event{ + Timestamp: time.Now(), + ID: t.Container.ID(), + Type: "start-container", + }) } } diff --git a/util/reaper.go b/util/reaper.go new file mode 100644 index 0000000..34b4c2f --- /dev/null +++ b/util/reaper.go @@ -0,0 +1,38 @@ +package util + +import ( + "syscall" + + "github.com/opencontainers/runc/libcontainer/utils" +) + +// Exit is the wait4 information from an exited process +type Exit struct { + Pid int + Status int +} + +// Reap reaps all child processes for the calling process and returns their +// exit information +func Reap() (exits []Exit, err error) { + var ( + ws syscall.WaitStatus + rus syscall.Rusage + ) + for { + pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus) + if err != nil { + if err == syscall.ECHILD { + return exits, nil + } + return exits, err + } + if pid <= 0 { + return exits, nil + } + exits = append(exits, Exit{ + Pid: pid, + Status: utils.ExitStatus(ws), + }) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/.gitignore b/vendor/src/github.com/opencontainers/runc/.gitignore new file mode 100644 index 0000000..41b8418 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/.gitignore @@ -0,0 +1,2 @@ +vendor/pkg +runc diff --git a/vendor/src/github.com/opencontainers/runc/CONTRIBUTING.md b/vendor/src/github.com/opencontainers/runc/CONTRIBUTING.md new file mode 100644 index 0000000..6f341f6 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/CONTRIBUTING.md @@ -0,0 +1,117 @@ +## Contribution Guidelines + +### Pull requests are always welcome + +We are always thrilled to receive pull requests, and do our best to +process them as fast as possible. Not sure if that typo is worth a pull +request? Do it! We will appreciate it. + +If your pull request is not accepted on the first try, don't be +discouraged! If there's a problem with the implementation, hopefully you +received feedback on what to improve. + +We're trying very hard to keep runc lean and focused. We don't want it +to do everything for everybody. This means that we might decide against +incorporating a new feature. However, there might be a way to implement +that feature *on top of* runc. + + +### Conventions + +Fork the repo and make changes on your fork in a feature branch: + +- If it's a bugfix branch, name it XXX-something where XXX is the number of the + issue +- If it's a feature branch, create an enhancement issue to announce your + intentions, and name it XXX-something where XXX is the number of the issue. + +Submit unit tests for your changes. Go has a great test framework built in; use +it! Take a look at existing tests for inspiration. Run the full test suite on +your branch before submitting a pull request. + +Update the documentation when creating or modifying features. Test +your documentation changes for clarity, concision, and correctness, as +well as a clean documentation build. See ``docs/README.md`` for more +information on building the docs and how docs get released. + +Write clean code. Universally formatted code promotes ease of writing, reading, +and maintenance. Always run `gofmt -s -w file.go` on each changed file before +committing your changes. Most editors have plugins that do this automatically. + +Pull requests descriptions should be as clear as possible and include a +reference to all the issues that they address. + +Pull requests must not contain commits from other users or branches. + +Commit messages must start with a capitalized and short summary (max. 50 +chars) written in the imperative, followed by an optional, more detailed +explanatory text which is separated from the summary by an empty line. + +Code review comments may be added to your pull request. Discuss, then make the +suggested modifications and push additional commits to your feature branch. Be +sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you +comment. + +Before the pull request is merged, make sure that you squash your commits into +logical units of work using `git rebase -i` and `git push -f`. After every +commit the test suite should be passing. Include documentation changes in the +same commit so that a revert would remove all traces of the feature or fix. + +Commits that fix or close an issue should include a reference like `Closes #XXX` +or `Fixes #XXX`, which will automatically close the issue when merged. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. The rules are pretty simple: if you +can certify the below (from +[developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign off when creating the git commit via `git commit -s`. diff --git a/vendor/src/github.com/opencontainers/runc/MAINTAINERS b/vendor/src/github.com/opencontainers/runc/MAINTAINERS new file mode 100644 index 0000000..5ce8037 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/MAINTAINERS @@ -0,0 +1,7 @@ +Michael Crosby (@crosbymichael) +Rohit Jnagal (@rjnagal) +Victor Marmol (@vmarmol) +Mrunal Patel (@mrunalp) +Alexander Morozov (@LK4D4) +Daniel, Dao Quang Minh (@dqminh) +Andrey Vagin (@avagin) diff --git a/vendor/src/github.com/opencontainers/runc/MAINTAINERS_GUIDE.md b/vendor/src/github.com/opencontainers/runc/MAINTAINERS_GUIDE.md new file mode 100644 index 0000000..caf27b5 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/MAINTAINERS_GUIDE.md @@ -0,0 +1,120 @@ +## Introduction + +Dear maintainer. Thank you for investing the time and energy to help +make runc as useful as possible. Maintaining a project is difficult, +sometimes unrewarding work. Sure, you will get to contribute cool +features to the project. But most of your time will be spent reviewing, +cleaning up, documenting, answering questions, justifying design +decisions - while everyone has all the fun! But remember - the quality +of the maintainers work is what distinguishes the good projects from the +great. So please be proud of your work, even the unglamourous parts, +and encourage a culture of appreciation and respect for *every* aspect +of improving the project - not just the hot new features. + +This document is a manual for maintainers old and new. It explains what +is expected of maintainers, how they should work, and what tools are +available to them. + +This is a living document - if you see something out of date or missing, +speak up! + +## What are a maintainer's responsibility? + +It is every maintainer's responsibility to: + +* 1) Expose a clear roadmap for improving their component. +* 2) Deliver prompt feedback and decisions on pull requests. +* 3) Be available to anyone with questions, bug reports, criticism etc. + on their component. This includes IRC and GitHub issues and pull requests. +* 4) Make sure their component respects the philosophy, design and + roadmap of the project. + +## How are decisions made? + +Short answer: with pull requests to the runc repository. + +runc is an open-source project with an open design philosophy. This +means that the repository is the source of truth for EVERY aspect of the +project, including its philosophy, design, roadmap and APIs. *If it's +part of the project, it's in the repo. It's in the repo, it's part of +the project.* + +As a result, all decisions can be expressed as changes to the +repository. An implementation change is a change to the source code. An +API change is a change to the API specification. A philosophy change is +a change to the philosophy manifesto. And so on. + +All decisions affecting runc, big and small, follow the same 3 steps: + +* Step 1: Open a pull request. Anyone can do this. + +* Step 2: Discuss the pull request. Anyone can do this. + +* Step 3: Accept (`LGTM`) or refuse a pull request. The relevant maintainers do +this (see below "Who decides what?") + +### I'm a maintainer, should I make pull requests too? + +Yes. Nobody should ever push to master directly. All changes should be +made through a pull request. + +## Who decides what? + +All decisions are pull requests, and the relevant maintainers make +decisions by accepting or refusing the pull request. Review and acceptance +by anyone is denoted by adding a comment in the pull request: `LGTM`. +However, only currently listed `MAINTAINERS` are counted towards the required +two LGTMs. + +Overall the maintainer system works because of mutual respect across the +maintainers of the project. The maintainers trust one another to make decisions +in the best interests of the project. Sometimes maintainers can disagree and +this is part of a healthy project to represent the point of views of various people. +In the case where maintainers cannot find agreement on a specific change the +role of a Chief Maintainer comes into play. + +The Chief Maintainer for the project is responsible for overall architecture +of the project to maintain conceptual integrity. Large decisions and +architecture changes should be reviewed by the chief maintainer. +The current chief maintainer for the project is Michael Crosby (@crosbymichael). + +Even though the maintainer system is built on trust, if there is a conflict +with the chief maintainer on a decision, their decision can be challenged +and brought to the technical oversight board if two-thirds of the +maintainers vote for an appeal. It is expected that this would be a +very exceptional event. + + +### How are maintainers added? + +The best maintainers have a vested interest in the project. Maintainers +are first and foremost contributors that have shown they are committed to +the long term success of the project. Contributors wanting to become +maintainers are expected to be deeply involved in contributing code, +pull request review, and triage of issues in the project for more than two months. + +Just contributing does not make you a maintainer, it is about building trust +with the current maintainers of the project and being a person that they can +depend on and trust to make decisions in the best interest of the project. The +final vote to add a new maintainer should be approved by over 66% of the current +maintainers with the chief maintainer having veto power. In case of a veto, +conflict resolution rules expressed above apply. The voting period is +five business days on the Pull Request to add the new maintainer. + + +### What is expected of maintainers? + +Part of a healthy project is to have active maintainers to support the community +in contributions and perform tasks to keep the project running. Maintainers are +expected to be able to respond in a timely manner if their help is required on specific +issues where they are pinged. Being a maintainer is a time consuming commitment and should +not be taken lightly. + +When a maintainer is unable to perform the required duties they can be removed with +a vote by 66% of the current maintainers with the chief maintainer having veto power. +The voting period is ten business days. Issues related to a maintainer's performance should +be discussed with them among the other maintainers so that they are not surprised by +a pull request removing them. + + + diff --git a/vendor/src/github.com/opencontainers/runc/Makefile b/vendor/src/github.com/opencontainers/runc/Makefile new file mode 100644 index 0000000..a708972 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/Makefile @@ -0,0 +1,40 @@ +RUNC_TEST_IMAGE=runc_test +PROJECT=github.com/opencontainers/runc +TEST_DOCKERFILE=script/test_Dockerfile +BUILDTAGS=seccomp +export GOPATH:=$(CURDIR)/Godeps/_workspace:$(GOPATH) + +all: + go build -tags "$(BUILDTAGS)" -o runc . + +static: + CGO_ENABLED=1 go build -tags "$(BUILDTAGS) cgo static_build" -ldflags "-w -extldflags -static" -o runc . + +vet: + go get golang.org/x/tools/cmd/vet + +lint: vet + go vet ./... + go fmt ./... + +runctestimage: + docker build -t $(RUNC_TEST_IMAGE) -f $(TEST_DOCKERFILE) . + +test: runctestimage + docker run -e TESTFLAGS --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_TEST_IMAGE) make localtest + +localtest: + go test -tags "$(BUILDTAGS)" ${TESTFLAGS} -v ./... + + +install: + cp runc /usr/local/bin/runc + +clean: + rm runc + +validate: vet + script/validate-gofmt + go vet ./... + +ci: validate localtest diff --git a/vendor/src/github.com/opencontainers/runc/NOTICE b/vendor/src/github.com/opencontainers/runc/NOTICE new file mode 100644 index 0000000..5c97abc --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/NOTICE @@ -0,0 +1,17 @@ +runc + +Copyright 2012-2015 Docker, Inc. + +This product includes software developed at Docker, Inc. (http://www.docker.com). + +The following is courtesy of our legal counsel: + + +Use and transfer of Docker may be subject to certain restrictions by the +United States and other governments. +It is your responsibility to ensure that your use and/or transfer does not +violate applicable laws. + +For more information, please see http://www.bis.doc.gov + +See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. diff --git a/vendor/src/github.com/opencontainers/runc/PRINCIPLES.md b/vendor/src/github.com/opencontainers/runc/PRINCIPLES.md new file mode 100644 index 0000000..fdcc373 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/PRINCIPLES.md @@ -0,0 +1,19 @@ +# runc principles + +In the design and development of runc and libcontainer we try to follow these principles: + +(Work in progress) + +* Don't try to replace every tool. Instead, be an ingredient to improve them. +* Less code is better. +* Fewer components are better. Do you really need to add one more class? +* 50 lines of straightforward, readable code is better than 10 lines of magic that nobody can understand. +* Don't do later what you can do now. "//TODO: refactor" is not acceptable in new code. +* When hesitating between two options, choose the one that is easier to reverse. +* "No" is temporary; "Yes" is forever. If you're not sure about a new feature, say no. You can change your mind later. +* Containers must be portable to the greatest possible number of machines. Be suspicious of any change which makes machines less interchangeable. +* The fewer moving parts in a container, the better. +* Don't merge it unless you document it. +* Don't document it unless you can keep it up-to-date. +* Don't merge it unless you test it! +* Everyone's problem is slightly different. Focus on the part that is the same for everyone, and solve that. diff --git a/vendor/src/github.com/opencontainers/runc/README.md b/vendor/src/github.com/opencontainers/runc/README.md new file mode 100644 index 0000000..1b5343d --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/README.md @@ -0,0 +1,144 @@ +[![Build Status](https://jenkins.dockerproject.org/buildStatus/icon?job=runc Master)](https://jenkins.dockerproject.org/job/runc Master) + +## runc + +`runc` is a CLI tool for spawning and running containers according to the OCF specification. + +## State of the project + +Currently `runc` is an implementation of the OCI specification. We are currently sprinting +to have a v1 of the spec out. So the `runc` config format will be constantly changing until +the spec is finalized. However, we encourage you to try out the tool and give feedback. + +### OCF + +How does `runc` integrate with the Open Container Initiative Specification? +`runc` depends on the types specified in the +[specs](https://github.com/opencontainers/specs) repository. Whenever the +specification is updated and ready to be versioned `runc` will update its dependency +on the specs repository and support the update spec. + +### Building: + +At the time of writing, runc only builds on the Linux platform. + +```bash +# create a 'github.com/opencontainers' in your GOPATH/src +cd github.com/opencontainers +git clone https://github.com/opencontainers/runc +cd runc +make +sudo make install +``` + +In order to enable seccomp support you will need to install libseccomp on your platform. +If you do not with to build `runc` with seccomp support you can add `BUILDTAGS=""` when running make. + +#### Build Tags + +`runc` supports optional build tags for compiling in support for various features. + + +| Build Tag | Feature | Dependency | +|-----------|------------------------------------|-------------| +| seccomp | Syscall filtering | libseccomp | +| selinux | selinux process and mount labeling | | +| apparmor | apparmor profile support | libapparmor | + +### Testing: + +You can run tests for runC by using command: + +```bash +# make test +``` + +Note that test cases are run in Docker container, so you need to install +`docker` first. And test requires mounting cgroups inside container, it's +done by docker now, so you need a docker version newer than 1.8.0-rc2. + +You can also run specific test cases by: + +```bash +# make test TESTFLAGS="-run=SomeTestFunction" +``` + +### Using: + +To run a container with the id "test", execute `runc start` with the containers id as arg one +in the bundle's root directory: + +```bash +runc start test +/ $ ps +PID USER COMMAND +1 daemon sh +5 daemon sh +/ $ +``` + +### OCI Container JSON Format: + +OCI container JSON format is based on OCI [specs](https://github.com/opencontainers/specs). +You can generate JSON files by using `runc spec`. +It assumes that the file-system is found in a directory called +`rootfs` and there is a user with uid and gid of `0` defined within that file-system. + +### Examples: + +#### Using a Docker image (requires version 1.3 or later) + +To test using Docker's `busybox` image follow these steps: +* Install `docker` and download the `busybox` image: `docker pull busybox` +* Create a container from that image and export its contents to a tar file: +`docker export $(docker create busybox) > busybox.tar` +* Untar the contents to create your filesystem directory: +``` +mkdir rootfs +tar -C rootfs -xf busybox.tar +``` +* Create `config.json` by using `runc spec`. +* Execute `runc start` and you should be placed into a shell where you can run `ps`: +``` +$ runc start test +/ # ps +PID USER COMMAND + 1 root sh + 9 root ps +``` + +#### Using runc with systemd + +To use runc with systemd, you can create a unit file +`/usr/lib/systemd/system/minecraft.service` as below (edit your +own Description or WorkingDirectory or service name as you need). + +```service +[Unit] +Description=Minecraft Build Server +Documentation=http://minecraft.net +After=network.target + +[Service] +CPUQuota=200% +MemoryLimit=1536M +ExecStart=/usr/local/bin/runc start minecraft +Restart=on-failure +WorkingDirectory=/containers/minecraftbuild + +[Install] +WantedBy=multi-user.target +``` + +Make sure you have the bundle's root directory and JSON configs in +your WorkingDirectory, then use systemd commands to start the service: + +```bash +systemctl daemon-reload +systemctl start minecraft.service +``` + +Note that if you use JSON configs by `runc spec`, you need to modify +`config.json` and change `process.terminal` to false so runc won't +create tty, because we can't set terminal from the stdin when using +systemd service. diff --git a/vendor/src/github.com/opencontainers/runc/checkpoint.go b/vendor/src/github.com/opencontainers/runc/checkpoint.go new file mode 100644 index 0000000..c1ca44f --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/checkpoint.go @@ -0,0 +1,84 @@ +// +build linux + +package main + +import ( + "fmt" + "strconv" + "strings" + + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" +) + +var checkpointCommand = cli.Command{ + Name: "checkpoint", + Usage: "checkpoint a running container", + Flags: []cli.Flag{ + cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"}, + cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"}, + cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"}, + cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"}, + cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"}, + cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"}, + cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"}, + cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"}, + cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: 'soft' (default), 'full' and 'strict'."}, + }, + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + defer destroy(container) + options := criuOptions(context) + // these are the mandatory criu options for a container + setPageServer(context, options) + setManageCgroupsMode(context, options) + if err := container.Checkpoint(options); err != nil { + fatal(err) + } + }, +} + +func getCheckpointImagePath(context *cli.Context) string { + imagePath := context.String("image-path") + if imagePath == "" { + imagePath = getDefaultImagePath(context) + } + return imagePath +} + +func setPageServer(context *cli.Context, options *libcontainer.CriuOpts) { + // xxx following criu opts are optional + // The dump image can be sent to a criu page server + if psOpt := context.String("page-server"); psOpt != "" { + addressPort := strings.Split(psOpt, ":") + if len(addressPort) != 2 { + fatal(fmt.Errorf("Use --page-server ADDRESS:PORT to specify page server")) + } + portInt, err := strconv.Atoi(addressPort[1]) + if err != nil { + fatal(fmt.Errorf("Invalid port number")) + } + options.PageServer = libcontainer.CriuPageServerInfo{ + Address: addressPort[0], + Port: int32(portInt), + } + } +} + +func setManageCgroupsMode(context *cli.Context, options *libcontainer.CriuOpts) { + if cgOpt := context.String("manage-cgroups-mode"); cgOpt != "" { + switch cgOpt { + case "soft": + options.ManageCgroupsMode = libcontainer.CRIU_CG_MODE_SOFT + case "full": + options.ManageCgroupsMode = libcontainer.CRIU_CG_MODE_FULL + case "strict": + options.ManageCgroupsMode = libcontainer.CRIU_CG_MODE_STRICT + default: + fatal(fmt.Errorf("Invalid manage cgroups mode")) + } + } +} diff --git a/vendor/src/github.com/opencontainers/runc/delete.go b/vendor/src/github.com/opencontainers/runc/delete.go new file mode 100644 index 0000000..41b3778 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/delete.go @@ -0,0 +1,15 @@ +package main + +import "github.com/codegangsta/cli" + +var deleteCommand = cli.Command{ + Name: "delete", + Usage: "delete any resources held by the container often used with detached containers", + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + destroy(container) + }, +} diff --git a/vendor/src/github.com/opencontainers/runc/events.go b/vendor/src/github.com/opencontainers/runc/events.go new file mode 100644 index 0000000..a4dbaed --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/events.go @@ -0,0 +1,95 @@ +// +build linux + +package main + +import ( + "encoding/json" + "os" + "sync" + "time" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" +) + +// event struct for encoding the event data to json. +type event struct { + Type string `json:"type"` + ID string `json:"id"` + Data interface{} `json:"data,omitempty"` +} + +var eventsCommand = cli.Command{ + Name: "events", + Usage: "display container events such as OOM notifications, cpu, memory, IO and network stats", + Flags: []cli.Flag{ + cli.DurationFlag{Name: "interval", Value: 5 * time.Second, Usage: "set the stats collection interval"}, + cli.BoolFlag{Name: "stats", Usage: "display the container's stats then exit"}, + }, + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + logrus.Fatal(err) + } + var ( + stats = make(chan *libcontainer.Stats, 1) + events = make(chan *event, 1024) + group = &sync.WaitGroup{} + ) + group.Add(1) + go func() { + defer group.Done() + enc := json.NewEncoder(os.Stdout) + for e := range events { + if err := enc.Encode(e); err != nil { + logrus.Error(err) + } + } + }() + if context.Bool("stats") { + s, err := container.Stats() + if err != nil { + fatal(err) + } + events <- &event{Type: "stats", ID: container.ID(), Data: s} + close(events) + group.Wait() + return + } + go func() { + for range time.Tick(context.Duration("interval")) { + s, err := container.Stats() + if err != nil { + logrus.Error(err) + continue + } + stats <- s + } + }() + n, err := container.NotifyOOM() + if err != nil { + logrus.Fatal(err) + } + for { + select { + case _, ok := <-n: + if ok { + // this means an oom event was received, if it is !ok then + // the channel was closed because the container stopped and + // the cgroups no longer exist. + events <- &event{Type: "oom", ID: container.ID()} + } else { + n = nil + } + case s := <-stats: + events <- &event{Type: "stats", ID: container.ID(), Data: s} + } + if n == nil { + close(events) + break + } + } + group.Wait() + }, +} diff --git a/vendor/src/github.com/opencontainers/runc/exec.go b/vendor/src/github.com/opencontainers/runc/exec.go new file mode 100644 index 0000000..5f7c9e7 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/exec.go @@ -0,0 +1,140 @@ +// +build linux + +package main + +import ( + "encoding/json" + "fmt" + "os" + "path" + "strconv" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/specs" +) + +var execCommand = cli.Command{ + Name: "exec", + Usage: "execute new process inside the container", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "console", + Usage: "specify the pty slave path for use with the container", + }, + cli.StringFlag{ + Name: "cwd", + Usage: "current working directory in the container", + }, + cli.StringSliceFlag{ + Name: "env, e", + Usage: "set environment variables", + }, + cli.BoolFlag{ + Name: "tty, t", + Usage: "allocate a pseudo-TTY", + }, + cli.StringFlag{ + Name: "user, u", + Usage: "UID (format: [:])", + }, + cli.StringFlag{ + Name: "process,p", + Usage: "path to the process.json", + }, + cli.BoolFlag{ + Name: "detach,d", + Usage: "detach from the container's process", + }, + cli.StringFlag{ + Name: "pid-file", + Value: "", + Usage: "specify the file to write the process id to", + }, + }, + Action: func(context *cli.Context) { + if os.Geteuid() != 0 { + logrus.Fatal("runc should be run as root") + } + status, err := execProcess(context) + if err != nil { + logrus.Fatalf("exec failed: %v", err) + } + os.Exit(status) + }, +} + +func execProcess(context *cli.Context) (int, error) { + container, err := getContainer(context) + if err != nil { + return -1, err + } + + var ( + detach = context.Bool("detach") + rootfs = container.Config().Rootfs + ) + + p, err := getProcess(context, path.Dir(rootfs)) + if err != nil { + return -1, err + } + + return runProcess(container, p, nil, context.String("console"), context.String("pid-file"), detach) + +} + +func getProcess(context *cli.Context, bundle string) (*specs.Process, error) { + if path := context.String("process"); path != "" { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + var p specs.Process + if err := json.NewDecoder(f).Decode(&p); err != nil { + return nil, err + } + return &p, nil + } + // process via cli flags + if err := os.Chdir(bundle); err != nil { + return nil, err + } + spec, err := loadSpec(specConfig) + if err != nil { + return nil, err + } + p := spec.Process + p.Args = context.Args()[1:] + // override the cwd, if passed + if context.String("cwd") != "" { + p.Cwd = context.String("cwd") + } + // append the passed env variables + for _, e := range context.StringSlice("env") { + p.Env = append(p.Env, e) + } + // set the tty + if context.IsSet("tty") { + p.Terminal = context.Bool("tty") + } + // override the user, if passed + if context.String("user") != "" { + u := strings.SplitN(context.String("user"), ":", 2) + if len(u) > 1 { + gid, err := strconv.Atoi(u[1]) + if err != nil { + return nil, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err) + } + p.User.GID = uint32(gid) + } + uid, err := strconv.Atoi(u[0]) + if err != nil { + return nil, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err) + } + p.User.UID = uint32(uid) + } + return &p, nil +} diff --git a/vendor/src/github.com/opencontainers/runc/kill.go b/vendor/src/github.com/opencontainers/runc/kill.go new file mode 100644 index 0000000..4040b59 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/kill.go @@ -0,0 +1,87 @@ +// +build linux + +package main + +import ( + "fmt" + "strconv" + "strings" + "syscall" + + "github.com/codegangsta/cli" +) + +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUS": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CLD": syscall.SIGCLD, + "CONT": syscall.SIGCONT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "POLL": syscall.SIGPOLL, + "PROF": syscall.SIGPROF, + "PWR": syscall.SIGPWR, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STKFLT": syscall.SIGSTKFLT, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "UNUSED": syscall.SIGUNUSED, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} + +var killCommand = cli.Command{ + Name: "kill", + Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process", + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + + sigstr := context.Args().Get(1) + if sigstr == "" { + sigstr = "SIGTERM" + } + + signal, err := parseSignal(sigstr) + if err != nil { + fatal(err) + } + + if err := container.Signal(signal); err != nil { + fatal(err) + } + }, +} + +func parseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + return syscall.Signal(s), nil + } + signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("unknown signal %q", rawSignal) + } + return signal, nil +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/README.md b/vendor/src/github.com/opencontainers/runc/libcontainer/README.md index 295edb4..fc6b4b0 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/README.md +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/README.md @@ -10,80 +10,165 @@ host system and which is (optionally) isolated from other containers in the syst #### Using libcontainer -To create a container you first have to initialize an instance of a factory -that will handle the creation and initialization for a container. - -Because containers are spawned in a two step process you will need to provide -arguments to a binary that will be executed as the init process for the container. -To use the current binary that is spawning the containers and acting as the parent -you can use `os.Args[0]` and we have a command called `init` setup. +Because containers are spawned in a two step process you will need a binary that +will be executed as the init process for the container. In libcontainer, we use +the current binary (/proc/self/exe) to be executed as the init process, and use +arg "init", we call the first step process "bootstrap", so you always need a "init" +function as the entry of "bootstrap". ```go -root, err := libcontainer.New("/var/lib/container", libcontainer.InitArgs(os.Args[0], "init")) +func init() { + if len(os.Args) > 1 && os.Args[1] == "init" { + runtime.GOMAXPROCS(1) + runtime.LockOSThread() + factory, _ := libcontainer.New("") + if err := factory.StartInitialization(); err != nil { + logrus.Fatal(err) + } + panic("--this line should have never been executed, congratulations--") + } +} +``` + +Then to create a container you first have to initialize an instance of a factory +that will handle the creation and initialization for a container. + +```go +factory, err := libcontainer.New("/var/lib/container", libcontainer.Cgroupfs, libcontainer.InitArgs(os.Args[0], "init")) if err != nil { - log.Fatal(err) + logrus.Fatal(err) + return } ``` Once you have an instance of the factory created we can create a configuration -struct describing how the container is to be created. A sample would look similar to this: +struct describing how the container is to be created. A sample would look similar to this: ```go +defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV config := &configs.Config{ - Rootfs: rootfs, - Capabilities: []string{ - "CAP_CHOWN", - "CAP_DAC_OVERRIDE", - "CAP_FSETID", - "CAP_FOWNER", - "CAP_MKNOD", - "CAP_NET_RAW", - "CAP_SETGID", - "CAP_SETUID", - "CAP_SETFCAP", - "CAP_SETPCAP", - "CAP_NET_BIND_SERVICE", - "CAP_SYS_CHROOT", - "CAP_KILL", - "CAP_AUDIT_WRITE", - }, - Namespaces: configs.Namespaces([]configs.Namespace{ - {Type: configs.NEWNS}, - {Type: configs.NEWUTS}, - {Type: configs.NEWIPC}, - {Type: configs.NEWPID}, - {Type: configs.NEWNET}, - }), - Cgroups: &configs.Cgroup{ - Name: "test-container", - Parent: "system", - AllowAllDevices: false, - AllowedDevices: configs.DefaultAllowedDevices, - }, - - Devices: configs.DefaultAutoCreatedDevices, - Hostname: "testing", - Networks: []*configs.Network{ - { - Type: "loopback", - Address: "127.0.0.1/0", - Gateway: "localhost", - }, - }, - Rlimits: []configs.Rlimit{ - { - Type: syscall.RLIMIT_NOFILE, - Hard: uint64(1024), - Soft: uint64(1024), - }, - }, + Rootfs: "/your/path/to/rootfs", + Capabilities: []string{ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE", + }, + Namespaces: configs.Namespaces([]configs.Namespace{ + {Type: configs.NEWNS}, + {Type: configs.NEWUTS}, + {Type: configs.NEWIPC}, + {Type: configs.NEWPID}, + {Type: configs.NEWUSER}, + {Type: configs.NEWNET}, + }), + Cgroups: &configs.Cgroup{ + Name: "test-container", + Parent: "system", + Resources: &configs.Resources{ + MemorySwappiness: -1, + AllowAllDevices: false, + AllowedDevices: configs.DefaultAllowedDevices, + }, + }, + MaskPaths: []string{ + "/proc/kcore", + }, + ReadonlyPaths: []string{ + "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", + }, + Devices: configs.DefaultAutoCreatedDevices, + Hostname: "testing", + Mounts: []*configs.Mount{ + { + Source: "proc", + Destination: "/proc", + Device: "proc", + Flags: defaultMountFlags, + }, + { + Source: "tmpfs", + Destination: "/dev", + Device: "tmpfs", + Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, + Data: "mode=755", + }, + { + Source: "devpts", + Destination: "/dev/pts", + Device: "devpts", + Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, + Data: "newinstance,ptmxmode=0666,mode=0620,gid=5", + }, + { + Device: "tmpfs", + Source: "shm", + Destination: "/dev/shm", + Data: "mode=1777,size=65536k", + Flags: defaultMountFlags, + }, + { + Source: "mqueue", + Destination: "/dev/mqueue", + Device: "mqueue", + Flags: defaultMountFlags, + }, + { + Source: "sysfs", + Destination: "/sys", + Device: "sysfs", + Flags: defaultMountFlags | syscall.MS_RDONLY, + }, + }, + UidMappings: []configs.IDMap{ + { + ContainerID: 0, + Host: 1000, + size: 65536, + }, + }, + GidMappings: []configs.IDMap{ + { + ContainerID: 0, + Host: 1000, + size: 65536, + }, + }, + Networks: []*configs.Network{ + { + Type: "loopback", + Address: "127.0.0.1/0", + Gateway: "localhost", + }, + }, + Rlimits: []configs.Rlimit{ + { + Type: syscall.RLIMIT_NOFILE, + Hard: uint64(1025), + Soft: uint64(1025), + }, + }, } ``` Once you have the configuration populated you can create a container: ```go -container, err := root.Create("container-id", config) +container, err := factory.Create("container-id", config) +if err != nil { + logrus.Fatal(err) + return +} ``` To spawn bash as the initial process inside the container and have the @@ -91,23 +176,25 @@ processes pid returned in order to wait, signal, or kill the process: ```go process := &libcontainer.Process{ - Args: []string{"/bin/bash"}, - Env: []string{"PATH=/bin"}, - User: "daemon", - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, + Args: []string{"/bin/bash"}, + Env: []string{"PATH=/bin"}, + User: "daemon", + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, } err := container.Start(process) if err != nil { - log.Fatal(err) + logrus.Fatal(err) + container.Destroy() + return } // wait for the process to finish. -status, err := process.Wait() +_, err := process.Wait() if err != nil { - log.Fatal(err) + logrus.Fatal(err) } // destroy the container. @@ -124,7 +211,6 @@ processes, err := container.Processes() // it's processes. stats, err := container.Stats() - // pause all processes inside the container. container.Pause() diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/SPEC.md b/vendor/src/github.com/opencontainers/runc/libcontainer/SPEC.md index 6151112..221545c 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/SPEC.md +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/SPEC.md @@ -142,6 +142,7 @@ system resources like cpu, memory, and device access. | perf_event | 1 | | freezer | 1 | | hugetlb | 1 | +| pids | 1 | All cgroup subsystem are joined so that statistics can be collected from @@ -199,7 +200,7 @@ provide a good default for security and flexibility for the applications. | CAP_SYS_BOOT | 0 | | CAP_LEASE | 0 | | CAP_WAKE_ALARM | 0 | -| CAP_BLOCK_SUSPE | 0 | +| CAP_BLOCK_SUSPEND | 0 | Additional security layers like [apparmor](https://wiki.ubuntu.com/AppArmor) diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go index a08e905..c8f7796 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go @@ -15,6 +15,9 @@ type Manager interface { // Returns the PIDs inside the cgroup set GetPids() ([]int, error) + // Returns the PIDs inside the cgroup set & all sub-cgroups + GetAllPids() ([]int, error) + // Returns statistics for the cgroup set GetStats() (*Stats, error) diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_test.go new file mode 100644 index 0000000..2f702bc --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_test.go @@ -0,0 +1,18 @@ +// +build linux + +package cgroups + +import ( + "testing" +) + +func TestParseCgroups(t *testing.T) { + cgroups, err := ParseCgroupFile("/proc/self/cgroup") + if err != nil { + t.Fatal(err) + } + + if _, ok := cgroups["cpu"]; !ok { + t.Fail() + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go index 0c4d207..21646e5 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/apply_raw.go @@ -23,6 +23,7 @@ var ( &MemoryGroup{}, &CpuGroup{}, &CpuacctGroup{}, + &PidsGroup{}, &BlkioGroup{}, &HugetlbGroup{}, &NetClsGroup{}, @@ -93,11 +94,10 @@ func getCgroupRoot() (string, error) { } type cgroupData struct { - root string - parent string - name string - config *configs.Cgroup - pid int + root string + innerPath string + config *configs.Cgroup + pid int } func (m *Manager) Apply(pid int) (err error) { @@ -112,6 +112,22 @@ func (m *Manager) Apply(pid int) (err error) { return err } + if c.Paths != nil { + paths := make(map[string]string) + for name, path := range c.Paths { + _, err := d.path(name) + if err != nil { + if cgroups.IsNotFound(err) { + continue + } + return err + } + paths[name] = path + } + m.Paths = paths + return cgroups.EnterPid(m.Paths, pid) + } + paths := make(map[string]string) defer func() { if err != nil { @@ -135,17 +151,13 @@ func (m *Manager) Apply(pid int) (err error) { paths[sys.Name()] = p } m.Paths = paths - - if paths["cpu"] != "" { - if err := CheckCpushares(paths["cpu"], c.Resources.CpuShares); err != nil { - return err - } - } - return nil } func (m *Manager) Destroy() error { + if m.Cgroups.Paths != nil { + return nil + } m.mu.Lock() defer m.mu.Unlock() if err := cgroups.RemovePaths(m.Paths); err != nil { @@ -179,15 +191,28 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) { } func (m *Manager) Set(container *configs.Config) error { - for name, path := range m.Paths { - sys, err := subsystems.Get(name) - if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { - continue + for _, sys := range subsystems { + // Generate fake cgroup data. + d, err := getCgroupData(container.Cgroups, -1) + if err != nil { + return err } + // Get the path, but don't error out if the cgroup wasn't found. + path, err := d.path(sys.Name()) + if err != nil && !cgroups.IsNotFound(err) { + return err + } + if err := sys.Set(path, container.Cgroups); err != nil { return err } } + + if m.Paths["cpu"] != "" { + if err := CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil { + return err + } + } return nil } @@ -217,31 +242,50 @@ func (m *Manager) Freeze(state configs.FreezerState) error { } func (m *Manager) GetPids() ([]int, error) { - d, err := getCgroupData(m.Cgroups, 0) + dir, err := getCgroupPath(m.Cgroups) if err != nil { return nil, err } - - dir, err := d.path("devices") - if err != nil { - return nil, err - } - return cgroups.GetPids(dir) } +func (m *Manager) GetAllPids() ([]int, error) { + dir, err := getCgroupPath(m.Cgroups) + if err != nil { + return nil, err + } + return cgroups.GetAllPids(dir) +} + +func getCgroupPath(c *configs.Cgroup) (string, error) { + d, err := getCgroupData(c, 0) + if err != nil { + return "", err + } + + return d.path("devices") +} + func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) { root, err := getCgroupRoot() if err != nil { return nil, err } + if (c.Name != "" || c.Parent != "") && c.Path != "" { + return nil, fmt.Errorf("cgroup: either Path or Name and Parent should be used") + } + + innerPath := c.Path + if innerPath == "" { + innerPath = filepath.Join(c.Parent, c.Name) + } + return &cgroupData{ - root: root, - parent: c.Parent, - name: c.Name, - config: c, - pid: pid, + root: root, + innerPath: c.Path, + config: c, + pid: pid, }, nil } @@ -269,11 +313,10 @@ func (raw *cgroupData) path(subsystem string) (string, error) { return "", err } - cgPath := filepath.Join(raw.parent, raw.name) // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. - if filepath.IsAbs(cgPath) { + if filepath.IsAbs(raw.innerPath) { // Sometimes subsystems can be mounted togethger as 'cpu,cpuacct'. - return filepath.Join(raw.root, filepath.Base(mnt), cgPath), nil + return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil } parentPath, err := raw.parentPath(subsystem, mnt, root) @@ -281,7 +324,7 @@ func (raw *cgroupData) path(subsystem string) (string, error) { return "", err } - return filepath.Join(parentPath, cgPath), nil + return filepath.Join(parentPath, raw.innerPath), nil } func (raw *cgroupData) join(subsystem string) (string, error) { diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go index 518cb63..a142cb9 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio.go @@ -22,15 +22,10 @@ func (s *BlkioGroup) Name() string { } func (s *BlkioGroup) Apply(d *cgroupData) error { - dir, err := d.join("blkio") + _, err := d.join("blkio") if err != nil && !cgroups.IsNotFound(err) { return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go new file mode 100644 index 0000000..6957392 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/blkio_test.go @@ -0,0 +1,636 @@ +// +build linux + +package fs + +import ( + "strconv" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" +) + +const ( + sectorsRecursiveContents = `8:0 1024` + serviceBytesRecursiveContents = `8:0 Read 100 +8:0 Write 200 +8:0 Sync 300 +8:0 Async 500 +8:0 Total 500 +Total 500` + servicedRecursiveContents = `8:0 Read 10 +8:0 Write 40 +8:0 Sync 20 +8:0 Async 30 +8:0 Total 50 +Total 50` + queuedRecursiveContents = `8:0 Read 1 +8:0 Write 4 +8:0 Sync 2 +8:0 Async 3 +8:0 Total 5 +Total 5` + serviceTimeRecursiveContents = `8:0 Read 173959 +8:0 Write 0 +8:0 Sync 0 +8:0 Async 173959 +8:0 Total 17395 +Total 17395` + waitTimeRecursiveContents = `8:0 Read 15571 +8:0 Write 0 +8:0 Sync 0 +8:0 Async 15571 +8:0 Total 15571` + mergedRecursiveContents = `8:0 Read 5 +8:0 Write 10 +8:0 Sync 0 +8:0 Async 0 +8:0 Total 15 +Total 15` + timeRecursiveContents = `8:0 8` + throttleServiceBytes = `8:0 Read 11030528 +8:0 Write 23 +8:0 Sync 42 +8:0 Async 11030528 +8:0 Total 11030528 +252:0 Read 11030528 +252:0 Write 23 +252:0 Sync 42 +252:0 Async 11030528 +252:0 Total 11030528 +Total 22061056` + throttleServiced = `8:0 Read 164 +8:0 Write 23 +8:0 Sync 42 +8:0 Async 164 +8:0 Total 164 +252:0 Read 164 +252:0 Write 23 +252:0 Sync 42 +252:0 Async 164 +252:0 Total 164 +Total 328` +) + +func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { + *blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op}) +} + +func TestBlkioSetWeight(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + weightBefore = 100 + weightAfter = 200 + ) + + helper.writeFileContents(map[string]string{ + "blkio.weight": strconv.Itoa(weightBefore), + }) + + helper.CgroupData.config.Resources.BlkioWeight = weightAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "blkio.weight") + if err != nil { + t.Fatalf("Failed to parse blkio.weight - %s", err) + } + + if value != weightAfter { + t.Fatal("Got the wrong value, set blkio.weight failed.") + } +} + +func TestBlkioSetWeightDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + weightDeviceBefore = "8:0 400" + ) + + wd := configs.NewWeightDevice(8, 0, 500, 0) + weightDeviceAfter := wd.WeightString() + + helper.writeFileContents(map[string]string{ + "blkio.weight_device": weightDeviceBefore, + }) + + helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd} + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device") + if err != nil { + t.Fatalf("Failed to parse blkio.weight_device - %s", err) + } + + if value != weightDeviceAfter { + t.Fatal("Got the wrong value, set blkio.weight_device failed.") + } +} + +// regression #274 +func TestBlkioSetMultipleWeightDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + weightDeviceBefore = "8:0 400" + ) + + wd1 := configs.NewWeightDevice(8, 0, 500, 0) + wd2 := configs.NewWeightDevice(8, 16, 500, 0) + // we cannot actually set and check both because normal ioutil.WriteFile + // when writing to cgroup file will overwrite the whole file content instead + // of updating it as the kernel is doing. Just check the second device + // is present will suffice for the test to ensure multiple writes are done. + weightDeviceAfter := wd2.WeightString() + + helper.writeFileContents(map[string]string{ + "blkio.weight_device": weightDeviceBefore, + }) + + helper.CgroupData.config.Resources.BlkioWeightDevice = []*configs.WeightDevice{wd1, wd2} + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device") + if err != nil { + t.Fatalf("Failed to parse blkio.weight_device - %s", err) + } + + if value != weightDeviceAfter { + t.Fatal("Got the wrong value, set blkio.weight_device failed.") + } +} + +func TestBlkioStats(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } + + // Verify expected stats. + expectedStats := cgroups.BlkioStats{} + appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "") + + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total") + + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total") + + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total") + + appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Read") + appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Write") + appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Sync") + appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Async") + appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 17395, "Total") + + appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Read") + appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Write") + appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Sync") + appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Async") + appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Total") + + appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 5, "Read") + appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 10, "Write") + appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Sync") + appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Async") + appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 15, "Total") + + appendBlkioStatEntry(&expectedStats.IoTimeRecursive, 8, 0, 8, "") + + expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) +} + +func TestBlkioStatsNoSectorsFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoServiceBytesFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoServicedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoQueuedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoServiceTimeFile(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoWaitTimeFile(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoMergedFile(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsNoTimeFile(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatalf("Failed unexpectedly: %s", err) + } +} + +func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read 100 100", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedFieldType(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read Write", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + "blkio.io_service_time_recursive": serviceTimeRecursiveContents, + "blkio.io_wait_time_recursive": waitTimeRecursiveContents, + "blkio.io_merged_recursive": mergedRecursiveContents, + "blkio.time_recursive": timeRecursiveContents, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestNonCFQBlkioStats(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "", + "blkio.io_serviced_recursive": "", + "blkio.io_queued_recursive": "", + "blkio.sectors_recursive": "", + "blkio.io_service_time_recursive": "", + "blkio.io_wait_time_recursive": "", + "blkio.io_merged_recursive": "", + "blkio.time_recursive": "", + "blkio.throttle.io_service_bytes": throttleServiceBytes, + "blkio.throttle.io_serviced": throttleServiced, + }) + + blkio := &BlkioGroup{} + actualStats := *cgroups.NewStats() + err := blkio.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } + + // Verify expected stats. + expectedStats := cgroups.BlkioStats{} + + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Read") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 23, "Write") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 42, "Sync") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Async") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Total") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Read") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 23, "Write") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 42, "Sync") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Async") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Total") + + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Read") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 23, "Write") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 42, "Sync") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Async") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Total") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Read") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 23, "Write") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 42, "Sync") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Async") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Total") + + expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) +} + +func TestBlkioSetThrottleReadBpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + throttleBefore = `8:0 1024` + ) + + td := configs.NewThrottleDevice(8, 0, 2048) + throttleAfter := td.String() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.read_bps_device": throttleBefore, + }) + + helper.CgroupData.config.Resources.BlkioThrottleReadBpsDevice = []*configs.ThrottleDevice{td} + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.") + } +} +func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + throttleBefore = `8:0 1024` + ) + + td := configs.NewThrottleDevice(8, 0, 2048) + throttleAfter := td.String() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.write_bps_device": throttleBefore, + }) + + helper.CgroupData.config.Resources.BlkioThrottleWriteBpsDevice = []*configs.ThrottleDevice{td} + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.") + } +} +func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + throttleBefore = `8:0 1024` + ) + + td := configs.NewThrottleDevice(8, 0, 2048) + throttleAfter := td.String() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.read_iops_device": throttleBefore, + }) + + helper.CgroupData.config.Resources.BlkioThrottleReadIOPSDevice = []*configs.ThrottleDevice{td} + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.") + } +} +func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + throttleBefore = `8:0 1024` + ) + + td := configs.NewThrottleDevice(8, 0, 2048) + throttleAfter := td.String() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.write_iops_device": throttleBefore, + }) + + helper.CgroupData.config.Resources.BlkioThrottleWriteIOPSDevice = []*configs.ThrottleDevice{td} + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go index ad5f427..a4ef28a 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu.go @@ -22,15 +22,10 @@ func (s *CpuGroup) Name() string { func (s *CpuGroup) Apply(d *cgroupData) error { // We always want to join the cpu group, to allow fair cpu scheduling // on a container basis - dir, err := d.join("cpu") + _, err := d.join("cpu") if err != nil && !cgroups.IsNotFound(err) { return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go new file mode 100644 index 0000000..554fd5e --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpu_test.go @@ -0,0 +1,163 @@ +// +build linux + +package fs + +import ( + "fmt" + "strconv" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" +) + +func TestCpuSetShares(t *testing.T) { + helper := NewCgroupTestUtil("cpu", t) + defer helper.cleanup() + + const ( + sharesBefore = 1024 + sharesAfter = 512 + ) + + helper.writeFileContents(map[string]string{ + "cpu.shares": strconv.Itoa(sharesBefore), + }) + + helper.CgroupData.config.Resources.CpuShares = sharesAfter + cpu := &CpuGroup{} + if err := cpu.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "cpu.shares") + if err != nil { + t.Fatalf("Failed to parse cpu.shares - %s", err) + } + + if value != sharesAfter { + t.Fatal("Got the wrong value, set cpu.shares failed.") + } +} + +func TestCpuSetBandWidth(t *testing.T) { + helper := NewCgroupTestUtil("cpu", t) + defer helper.cleanup() + + const ( + quotaBefore = 8000 + quotaAfter = 5000 + periodBefore = 10000 + periodAfter = 7000 + rtRuntimeBefore = 8000 + rtRuntimeAfter = 5000 + rtPeriodBefore = 10000 + rtPeriodAfter = 7000 + ) + + helper.writeFileContents(map[string]string{ + "cpu.cfs_quota_us": strconv.Itoa(quotaBefore), + "cpu.cfs_period_us": strconv.Itoa(periodBefore), + "cpu.rt_runtime_us": strconv.Itoa(rtRuntimeBefore), + "cpu.rt_period_us": strconv.Itoa(rtPeriodBefore), + }) + + helper.CgroupData.config.Resources.CpuQuota = quotaAfter + helper.CgroupData.config.Resources.CpuPeriod = periodAfter + helper.CgroupData.config.Resources.CpuRtRuntime = rtRuntimeAfter + helper.CgroupData.config.Resources.CpuRtPeriod = rtPeriodAfter + cpu := &CpuGroup{} + if err := cpu.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + quota, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_quota_us") + if err != nil { + t.Fatalf("Failed to parse cpu.cfs_quota_us - %s", err) + } + if quota != quotaAfter { + t.Fatal("Got the wrong value, set cpu.cfs_quota_us failed.") + } + + period, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_period_us") + if err != nil { + t.Fatalf("Failed to parse cpu.cfs_period_us - %s", err) + } + if period != periodAfter { + t.Fatal("Got the wrong value, set cpu.cfs_period_us failed.") + } + rtRuntime, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_runtime_us") + if err != nil { + t.Fatalf("Failed to parse cpu.rt_runtime_us - %s", err) + } + if rtRuntime != rtRuntimeAfter { + t.Fatal("Got the wrong value, set cpu.rt_runtime_us failed.") + } + rtPeriod, err := getCgroupParamUint(helper.CgroupPath, "cpu.rt_period_us") + if err != nil { + t.Fatalf("Failed to parse cpu.rt_period_us - %s", err) + } + if rtPeriod != rtPeriodAfter { + t.Fatal("Got the wrong value, set cpu.rt_period_us failed.") + } +} + +func TestCpuStats(t *testing.T) { + helper := NewCgroupTestUtil("cpu", t) + defer helper.cleanup() + + const ( + kNrPeriods = 2000 + kNrThrottled = 200 + kThrottledTime = uint64(18446744073709551615) + ) + + cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n", + kNrPeriods, kNrThrottled, kThrottledTime) + helper.writeFileContents(map[string]string{ + "cpu.stat": cpuStatContent, + }) + + cpu := &CpuGroup{} + actualStats := *cgroups.NewStats() + err := cpu.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } + + expectedStats := cgroups.ThrottlingData{ + Periods: kNrPeriods, + ThrottledPeriods: kNrThrottled, + ThrottledTime: kThrottledTime} + + expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData) +} + +func TestNoCpuStatFile(t *testing.T) { + helper := NewCgroupTestUtil("cpu", t) + defer helper.cleanup() + + cpu := &CpuGroup{} + actualStats := *cgroups.NewStats() + err := cpu.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal("Expected not to fail, but did") + } +} + +func TestInvalidCpuStat(t *testing.T) { + helper := NewCgroupTestUtil("cpu", t) + defer helper.cleanup() + cpuStatContent := `nr_periods 2000 + nr_throttled 200 + throttled_time fortytwo` + helper.writeFileContents(map[string]string{ + "cpu.stat": cpuStatContent, + }) + + cpu := &CpuGroup{} + actualStats := *cgroups.NewStats() + err := cpu.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failed stat parsing.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go index b763210..cbe62bd 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset.go @@ -4,6 +4,7 @@ package fs import ( "bytes" + "fmt" "io/ioutil" "os" "path/filepath" @@ -11,6 +12,7 @@ import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" + libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" ) type CpusetGroup struct { @@ -63,11 +65,6 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro if err := s.ensureParent(dir, root); err != nil { return err } - // the default values inherit from parent cgroup are already set in - // s.ensureParent, cover these if we have our own - if err := s.Set(dir, cgroup); err != nil { - return err - } // because we are not using d.join we need to place the pid into the procs file // unlike the other subsystems if err := writeFile(dir, "cgroup.procs", strconv.Itoa(pid)); err != nil { @@ -92,9 +89,13 @@ func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []b // it's parent. func (s *CpusetGroup) ensureParent(current, root string) error { parent := filepath.Dir(current) - if filepath.Clean(parent) == root { + if libcontainerUtils.CleanPath(parent) == root { return nil } + // Avoid infinite recursion. + if parent == current { + return fmt.Errorf("cpuset: cgroup parent path outside cgroup root") + } if err := s.ensureParent(parent, root); err != nil { return err } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go new file mode 100644 index 0000000..0f92915 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/cpuset_test.go @@ -0,0 +1,65 @@ +// +build linux + +package fs + +import ( + "testing" +) + +func TestCpusetSetCpus(t *testing.T) { + helper := NewCgroupTestUtil("cpuset", t) + defer helper.cleanup() + + const ( + cpusBefore = "0" + cpusAfter = "1-3" + ) + + helper.writeFileContents(map[string]string{ + "cpuset.cpus": cpusBefore, + }) + + helper.CgroupData.config.Resources.CpusetCpus = cpusAfter + cpuset := &CpusetGroup{} + if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "cpuset.cpus") + if err != nil { + t.Fatalf("Failed to parse cpuset.cpus - %s", err) + } + + if value != cpusAfter { + t.Fatal("Got the wrong value, set cpuset.cpus failed.") + } +} + +func TestCpusetSetMems(t *testing.T) { + helper := NewCgroupTestUtil("cpuset", t) + defer helper.cleanup() + + const ( + memsBefore = "0" + memsAfter = "1" + ) + + helper.writeFileContents(map[string]string{ + "cpuset.mems": memsBefore, + }) + + helper.CgroupData.config.Resources.CpusetMems = memsAfter + cpuset := &CpusetGroup{} + if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "cpuset.mems") + if err != nil { + t.Fatalf("Failed to parse cpuset.mems - %s", err) + } + + if value != memsAfter { + t.Fatal("Got the wrong value, set cpuset.mems failed.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go index a9883eb..4969798 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices.go @@ -15,21 +15,29 @@ func (s *DevicesGroup) Name() string { } func (s *DevicesGroup) Apply(d *cgroupData) error { - dir, err := d.join("devices") + _, err := d.join("devices") if err != nil { // We will return error even it's `not found` error, devices // cgroup is hard requirement for container's security. return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { + devices := cgroup.Resources.Devices + if len(devices) > 0 { + for _, dev := range devices { + file := "devices.deny" + if dev.Allow { + file = "devices.allow" + } + if err := writeFile(path, file, dev.CgroupString()); err != nil { + return err + } + } + return nil + } if !cgroup.Resources.AllowAllDevices { if err := writeFile(path, "devices.deny", "a"); err != nil { return err diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go new file mode 100644 index 0000000..ee44084 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/devices_test.go @@ -0,0 +1,84 @@ +// +build linux + +package fs + +import ( + "testing" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +var ( + allowedDevices = []*configs.Device{ + { + Path: "/dev/zero", + Type: 'c', + Major: 1, + Minor: 5, + Permissions: "rwm", + FileMode: 0666, + }, + } + allowedList = "c 1:5 rwm" + deniedDevices = []*configs.Device{ + { + Path: "/dev/null", + Type: 'c', + Major: 1, + Minor: 3, + Permissions: "rwm", + FileMode: 0666, + }, + } + deniedList = "c 1:3 rwm" +) + +func TestDevicesSetAllow(t *testing.T) { + helper := NewCgroupTestUtil("devices", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "devices.deny": "a", + }) + + helper.CgroupData.config.Resources.AllowAllDevices = false + helper.CgroupData.config.Resources.AllowedDevices = allowedDevices + devices := &DevicesGroup{} + if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "devices.allow") + if err != nil { + t.Fatalf("Failed to parse devices.allow - %s", err) + } + + if value != allowedList { + t.Fatal("Got the wrong value, set devices.allow failed.") + } +} + +func TestDevicesSetDeny(t *testing.T) { + helper := NewCgroupTestUtil("devices", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "devices.allow": "a", + }) + + helper.CgroupData.config.Resources.AllowAllDevices = true + helper.CgroupData.config.Resources.DeniedDevices = deniedDevices + devices := &DevicesGroup{} + if err := devices.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "devices.deny") + if err != nil { + t.Fatalf("Failed to parse devices.deny - %s", err) + } + + if value != deniedList { + t.Fatal("Got the wrong value, set devices.deny failed.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go index 6aaad4e..e70dfe3 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer.go @@ -19,15 +19,10 @@ func (s *FreezerGroup) Name() string { } func (s *FreezerGroup) Apply(d *cgroupData) error { - dir, err := d.join("freezer") + _, err := d.join("freezer") if err != nil && !cgroups.IsNotFound(err) { return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go new file mode 100644 index 0000000..77708db --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/freezer_test.go @@ -0,0 +1,47 @@ +// +build linux + +package fs + +import ( + "testing" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +func TestFreezerSetState(t *testing.T) { + helper := NewCgroupTestUtil("freezer", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "freezer.state": string(configs.Frozen), + }) + + helper.CgroupData.config.Resources.Freezer = configs.Thawed + freezer := &FreezerGroup{} + if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "freezer.state") + if err != nil { + t.Fatalf("Failed to parse freezer.state - %s", err) + } + if value != string(configs.Thawed) { + t.Fatal("Got the wrong value, set freezer.state failed.") + } +} + +func TestFreezerSetInvalidState(t *testing.T) { + helper := NewCgroupTestUtil("freezer", t) + defer helper.cleanup() + + const ( + invalidArg configs.FreezerState = "Invalid" + ) + + helper.CgroupData.config.Resources.Freezer = invalidArg + freezer := &FreezerGroup{} + if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err == nil { + t.Fatal("Failed to return invalid argument error") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go index ca106da..2f97277 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb.go @@ -19,15 +19,10 @@ func (s *HugetlbGroup) Name() string { } func (s *HugetlbGroup) Apply(d *cgroupData) error { - dir, err := d.join("hugetlb") + _, err := d.join("hugetlb") if err != nil && !cgroups.IsNotFound(err) { return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go new file mode 100644 index 0000000..2d41c4e --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/hugetlb_test.go @@ -0,0 +1,154 @@ +// +build linux + +package fs + +import ( + "fmt" + "strconv" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" +) + +const ( + hugetlbUsageContents = "128\n" + hugetlbMaxUsageContents = "256\n" + hugetlbFailcnt = "100\n" +) + +var ( + usage = "hugetlb.%s.usage_in_bytes" + limit = "hugetlb.%s.limit_in_bytes" + maxUsage = "hugetlb.%s.max_usage_in_bytes" + failcnt = "hugetlb.%s.failcnt" +) + +func TestHugetlbSetHugetlb(t *testing.T) { + helper := NewCgroupTestUtil("hugetlb", t) + defer helper.cleanup() + + const ( + hugetlbBefore = 256 + hugetlbAfter = 512 + ) + + for _, pageSize := range HugePageSizes { + helper.writeFileContents(map[string]string{ + fmt.Sprintf(limit, pageSize): strconv.Itoa(hugetlbBefore), + }) + } + + for _, pageSize := range HugePageSizes { + helper.CgroupData.config.Resources.HugetlbLimit = []*configs.HugepageLimit{ + { + Pagesize: pageSize, + Limit: hugetlbAfter, + }, + } + hugetlb := &HugetlbGroup{} + if err := hugetlb.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + } + + for _, pageSize := range HugePageSizes { + limit := fmt.Sprintf(limit, pageSize) + value, err := getCgroupParamUint(helper.CgroupPath, limit) + if err != nil { + t.Fatalf("Failed to parse %s - %s", limit, err) + } + if value != hugetlbAfter { + t.Fatalf("Set hugetlb.limit_in_bytes failed. Expected: %v, Got: %v", hugetlbAfter, value) + } + } +} + +func TestHugetlbStats(t *testing.T) { + helper := NewCgroupTestUtil("hugetlb", t) + defer helper.cleanup() + for _, pageSize := range HugePageSizes { + helper.writeFileContents(map[string]string{ + fmt.Sprintf(usage, pageSize): hugetlbUsageContents, + fmt.Sprintf(maxUsage, pageSize): hugetlbMaxUsageContents, + fmt.Sprintf(failcnt, pageSize): hugetlbFailcnt, + }) + } + + hugetlb := &HugetlbGroup{} + actualStats := *cgroups.NewStats() + err := hugetlb.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } + expectedStats := cgroups.HugetlbStats{Usage: 128, MaxUsage: 256, Failcnt: 100} + for _, pageSize := range HugePageSizes { + expectHugetlbStatEquals(t, expectedStats, actualStats.HugetlbStats[pageSize]) + } +} + +func TestHugetlbStatsNoUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("hugetlb", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + maxUsage: hugetlbMaxUsageContents, + }) + + hugetlb := &HugetlbGroup{} + actualStats := *cgroups.NewStats() + err := hugetlb.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestHugetlbStatsNoMaxUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("hugetlb", t) + defer helper.cleanup() + for _, pageSize := range HugePageSizes { + helper.writeFileContents(map[string]string{ + fmt.Sprintf(usage, pageSize): hugetlbUsageContents, + }) + } + + hugetlb := &HugetlbGroup{} + actualStats := *cgroups.NewStats() + err := hugetlb.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestHugetlbStatsBadUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("hugetlb", t) + defer helper.cleanup() + for _, pageSize := range HugePageSizes { + helper.writeFileContents(map[string]string{ + fmt.Sprintf(usage, pageSize): "bad", + maxUsage: hugetlbMaxUsageContents, + }) + } + + hugetlb := &HugetlbGroup{} + actualStats := *cgroups.NewStats() + err := hugetlb.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestHugetlbStatsBadMaxUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("hugetlb", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + usage: hugetlbUsageContents, + maxUsage: "bad", + }) + + hugetlb := &HugetlbGroup{} + actualStats := *cgroups.NewStats() + err := hugetlb.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go index 6b9687c..2121f6d 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go @@ -32,8 +32,9 @@ func (s *MemoryGroup) Apply(d *cgroupData) (err error) { return err } } - - if err := s.Set(path, d.config); err != nil { + // We have to set kernel memory here, as we can't change it once + // processes have been attached. + if err := s.SetKernelMemory(path, d.config); err != nil { return err } } @@ -50,7 +51,17 @@ func (s *MemoryGroup) Apply(d *cgroupData) (err error) { if err != nil && !cgroups.IsNotFound(err) { return err } + return nil +} +func (s *MemoryGroup) SetKernelMemory(path string, cgroup *configs.Cgroup) error { + // This has to be done separately because it has special constraints (it + // can't be done after there are processes attached to the cgroup). + if cgroup.Resources.KernelMemory > 0 { + if err := writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemory, 10)); err != nil { + return err + } + } return nil } @@ -70,12 +81,6 @@ func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error { return err } } - if cgroup.Resources.KernelMemory > 0 { - if err := writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemory, 10)); err != nil { - return err - } - } - if cgroup.Resources.OomKillDisable { if err := writeFile(path, "memory.oom_control", "1"); err != nil { return err @@ -157,6 +162,7 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) { usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".") maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".") failcnt := strings.Join([]string{moduleName, "failcnt"}, ".") + limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".") value, err := getCgroupParamUint(path, usage) if err != nil { @@ -182,6 +188,14 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) { return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err) } memoryData.Failcnt = value + value, err = getCgroupParamUint(path, limit) + if err != nil { + if moduleName != "memory" && os.IsNotExist(err) { + return cgroups.MemoryData{}, nil + } + return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err) + } + memoryData.Limit = value return memoryData, nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go new file mode 100644 index 0000000..6dc4ae7 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory_test.go @@ -0,0 +1,339 @@ +// +build linux + +package fs + +import ( + "strconv" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" +) + +const ( + memoryStatContents = `cache 512 +rss 1024` + memoryUsageContents = "2048\n" + memoryMaxUsageContents = "4096\n" + memoryFailcnt = "100\n" + memoryLimitContents = "8192\n" +) + +func TestMemorySetMemory(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + + const ( + memoryBefore = 314572800 // 300M + memoryAfter = 524288000 // 500M + reservationBefore = 209715200 // 200M + reservationAfter = 314572800 // 300M + ) + + helper.writeFileContents(map[string]string{ + "memory.limit_in_bytes": strconv.Itoa(memoryBefore), + "memory.soft_limit_in_bytes": strconv.Itoa(reservationBefore), + }) + + helper.CgroupData.config.Resources.Memory = memoryAfter + helper.CgroupData.config.Resources.MemoryReservation = reservationAfter + memory := &MemoryGroup{} + if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes") + if err != nil { + t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err) + } + if value != memoryAfter { + t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.") + } + + value, err = getCgroupParamUint(helper.CgroupPath, "memory.soft_limit_in_bytes") + if err != nil { + t.Fatalf("Failed to parse memory.soft_limit_in_bytes - %s", err) + } + if value != reservationAfter { + t.Fatal("Got the wrong value, set memory.soft_limit_in_bytes failed.") + } +} + +func TestMemorySetMemoryswap(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + + const ( + memoryswapBefore = 314572800 // 300M + memoryswapAfter = 524288000 // 500M + ) + + helper.writeFileContents(map[string]string{ + "memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore), + }) + + helper.CgroupData.config.Resources.MemorySwap = memoryswapAfter + memory := &MemoryGroup{} + if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes") + if err != nil { + t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err) + } + if value != memoryswapAfter { + t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.") + } +} + +func TestMemorySetKernelMemory(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + + const ( + kernelMemoryBefore = 314572800 // 300M + kernelMemoryAfter = 524288000 // 500M + ) + + helper.writeFileContents(map[string]string{ + "memory.kmem.limit_in_bytes": strconv.Itoa(kernelMemoryBefore), + }) + + helper.CgroupData.config.Resources.KernelMemory = kernelMemoryAfter + memory := &MemoryGroup{} + if err := memory.SetKernelMemory(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "memory.kmem.limit_in_bytes") + if err != nil { + t.Fatalf("Failed to parse memory.kmem.limit_in_bytes - %s", err) + } + if value != kernelMemoryAfter { + t.Fatal("Got the wrong value, set memory.kmem.limit_in_bytes failed.") + } +} + +func TestMemorySetMemorySwappinessDefault(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + + const ( + swappinessBefore = 60 //deafult is 60 + swappinessAfter = 0 + ) + + helper.writeFileContents(map[string]string{ + "memory.swappiness": strconv.Itoa(swappinessBefore), + }) + + helper.CgroupData.config.Resources.Memory = swappinessAfter + memory := &MemoryGroup{} + if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "memory.swappiness") + if err != nil { + t.Fatalf("Failed to parse memory.swappiness - %s", err) + } + if value != swappinessAfter { + t.Fatal("Got the wrong value, set memory.swappiness failed.") + } +} + +func TestMemoryStats(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.usage_in_bytes": memoryUsageContents, + "memory.limit_in_bytes": memoryLimitContents, + "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.failcnt": memoryFailcnt, + "memory.memsw.usage_in_bytes": memoryUsageContents, + "memory.memsw.max_usage_in_bytes": memoryMaxUsageContents, + "memory.memsw.failcnt": memoryFailcnt, + "memory.memsw.limit_in_bytes": memoryLimitContents, + "memory.kmem.usage_in_bytes": memoryUsageContents, + "memory.kmem.max_usage_in_bytes": memoryMaxUsageContents, + "memory.kmem.failcnt": memoryFailcnt, + "memory.kmem.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } + expectedStats := cgroups.MemoryStats{Cache: 512, Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, Stats: map[string]uint64{"cache": 512, "rss": 1024}} + expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) +} + +func TestMemoryStatsNoStatFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.usage_in_bytes": memoryUsageContents, + "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } +} + +func TestMemoryStatsNoUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemoryStatsNoMaxUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.usage_in_bytes": memoryUsageContents, + "memory.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemoryStatsNoLimitInBytesFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.usage_in_bytes": memoryUsageContents, + "memory.max_usage_in_bytes": memoryMaxUsageContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemoryStatsBadStatFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": "rss rss", + "memory.usage_in_bytes": memoryUsageContents, + "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemoryStatsBadUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.usage_in_bytes": "bad", + "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemoryStatsBadMaxUsageFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.usage_in_bytes": memoryUsageContents, + "memory.max_usage_in_bytes": "bad", + "memory.limit_in_bytes": memoryLimitContents, + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemoryStatsBadLimitInBytesFile(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "memory.stat": memoryStatContents, + "memory.usage_in_bytes": memoryUsageContents, + "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.limit_in_bytes": "bad", + }) + + memory := &MemoryGroup{} + actualStats := *cgroups.NewStats() + err := memory.GetStats(helper.CgroupPath, &actualStats) + if err == nil { + t.Fatal("Expected failure") + } +} + +func TestMemorySetOomControl(t *testing.T) { + helper := NewCgroupTestUtil("memory", t) + defer helper.cleanup() + + const ( + oom_kill_disable = 1 // disable oom killer, default is 0 + ) + + helper.writeFileContents(map[string]string{ + "memory.oom_control": strconv.Itoa(oom_kill_disable), + }) + + memory := &MemoryGroup{} + if err := memory.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "memory.oom_control") + if err != nil { + t.Fatalf("Failed to parse memory.oom_control - %s", err) + } + + if value != oom_kill_disable { + t.Fatalf("Got the wrong value, set memory.oom_control failed.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go index 6382373..8a4054b 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls.go @@ -15,15 +15,10 @@ func (s *NetClsGroup) Name() string { } func (s *NetClsGroup) Apply(d *cgroupData) error { - dir, err := d.join("net_cls") + _, err := d.join("net_cls") if err != nil && !cgroups.IsNotFound(err) { return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go new file mode 100644 index 0000000..974bd9d --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_cls_test.go @@ -0,0 +1,38 @@ +// +build linux + +package fs + +import ( + "testing" +) + +const ( + classidBefore = "0x100002" + classidAfter = "0x100001" +) + +func TestNetClsSetClassid(t *testing.T) { + helper := NewCgroupTestUtil("net_cls", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "net_cls.classid": classidBefore, + }) + + helper.CgroupData.config.Resources.NetClsClassid = classidAfter + netcls := &NetClsGroup{} + if err := netcls.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + // As we are in mock environment, we can't get correct value of classid from + // net_cls.classid. + // So. we just judge if we successfully write classid into file + value, err := getCgroupParamString(helper.CgroupPath, "net_cls.classid") + if err != nil { + t.Fatalf("Failed to parse net_cls.classid - %s", err) + } + if value != classidAfter { + t.Fatal("Got the wrong value, set net_cls.classid failed.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go index 0dabaae..d0ab2af 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio.go @@ -15,15 +15,10 @@ func (s *NetPrioGroup) Name() string { } func (s *NetPrioGroup) Apply(d *cgroupData) error { - dir, err := d.join("net_prio") + _, err := d.join("net_prio") if err != nil && !cgroups.IsNotFound(err) { return err } - - if err := s.Set(dir, d.config); err != nil { - return err - } - return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go new file mode 100644 index 0000000..efbf063 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/net_prio_test.go @@ -0,0 +1,38 @@ +// +build linux + +package fs + +import ( + "strings" + "testing" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +var ( + prioMap = []*configs.IfPrioMap{ + { + Interface: "test", + Priority: 5, + }, + } +) + +func TestNetPrioSetIfPrio(t *testing.T) { + helper := NewCgroupTestUtil("net_prio", t) + defer helper.cleanup() + + helper.CgroupData.config.Resources.NetPrioIfpriomap = prioMap + netPrio := &NetPrioGroup{} + if err := netPrio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "net_prio.ifpriomap") + if err != nil { + t.Fatalf("Failed to parse net_prio.ifpriomap - %s", err) + } + if !strings.Contains(value, "test 5") { + t.Fatal("Got the wrong value, set net_prio.ifpriomap failed.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go new file mode 100644 index 0000000..96cbb89 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids.go @@ -0,0 +1,57 @@ +// +build linux + +package fs + +import ( + "fmt" + "strconv" + + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" +) + +type PidsGroup struct { +} + +func (s *PidsGroup) Name() string { + return "pids" +} + +func (s *PidsGroup) Apply(d *cgroupData) error { + _, err := d.join("pids") + if err != nil && !cgroups.IsNotFound(err) { + return err + } + return nil +} + +func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error { + if cgroup.Resources.PidsLimit != 0 { + // "max" is the fallback value. + limit := "max" + + if cgroup.Resources.PidsLimit > 0 { + limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10) + } + + if err := writeFile(path, "pids.max", limit); err != nil { + return err + } + } + + return nil +} + +func (s *PidsGroup) Remove(d *cgroupData) error { + return removePath(d.path("pids")) +} + +func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error { + value, err := getCgroupParamUint(path, "pids.current") + if err != nil { + return fmt.Errorf("failed to parse pids.current - %s", err) + } + + stats.PidsStats.Current = value + return nil +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go new file mode 100644 index 0000000..06b1192 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/pids_test.go @@ -0,0 +1,83 @@ +// +build linux + +package fs + +import ( + "strconv" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" +) + +const ( + maxUnlimited = -1 + maxLimited = 1024 +) + +func TestPidsSetMax(t *testing.T) { + helper := NewCgroupTestUtil("pids", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "pids.max": "max", + }) + + helper.CgroupData.config.Resources.PidsLimit = maxLimited + pids := &PidsGroup{} + if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamUint(helper.CgroupPath, "pids.max") + if err != nil { + t.Fatalf("Failed to parse pids.max - %s", err) + } + + if value != maxLimited { + t.Fatalf("Expected %d, got %d for setting pids.max - limited", maxLimited, value) + } +} + +func TestPidsSetUnlimited(t *testing.T) { + helper := NewCgroupTestUtil("pids", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "pids.max": strconv.Itoa(maxLimited), + }) + + helper.CgroupData.config.Resources.PidsLimit = maxUnlimited + pids := &PidsGroup{} + if err := pids.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "pids.max") + if err != nil { + t.Fatalf("Failed to parse pids.max - %s", err) + } + + if value != "max" { + t.Fatalf("Expected %s, got %s for setting pids.max - unlimited", "max", value) + } +} + +func TestPidsStats(t *testing.T) { + helper := NewCgroupTestUtil("pids", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "pids.current": strconv.Itoa(1337), + "pids.max": strconv.Itoa(maxLimited), + }) + + pids := &PidsGroup{} + stats := *cgroups.NewStats() + if err := pids.GetStats(helper.CgroupPath, &stats); err != nil { + t.Fatal(err) + } + + if stats.PidsStats.Current != 1337 { + t.Fatalf("Expected %d, got %d for pids.current", 1337, stats.PidsStats.Current) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go new file mode 100644 index 0000000..295e7bd --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/stats_util_test.go @@ -0,0 +1,117 @@ +// +build linux + +package fs + +import ( + "fmt" + "testing" + + "github.com/Sirupsen/logrus" + "github.com/opencontainers/runc/libcontainer/cgroups" +) + +func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error { + if len(expected) != len(actual) { + return fmt.Errorf("blkioStatEntries length do not match") + } + for i, expValue := range expected { + actValue := actual[i] + if expValue != actValue { + return fmt.Errorf("Expected blkio stat entry %v but found %v", expValue, actValue) + } + } + return nil +} + +func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) { + if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil { + logrus.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil { + logrus.Printf("blkio IoServicedRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil { + logrus.Printf("blkio IoQueuedRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil { + logrus.Printf("blkio SectorsRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoServiceTimeRecursive, actual.IoServiceTimeRecursive); err != nil { + logrus.Printf("blkio IoServiceTimeRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoWaitTimeRecursive, actual.IoWaitTimeRecursive); err != nil { + logrus.Printf("blkio IoWaitTimeRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil { + logrus.Printf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoTimeRecursive, actual.IoTimeRecursive); err != nil { + logrus.Printf("blkio IoTimeRecursive do not match - %s\n", err) + t.Fail() + } +} + +func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) { + if expected != actual { + logrus.Printf("Expected throttling data %v but found %v\n", expected, actual) + t.Fail() + } +} + +func expectHugetlbStatEquals(t *testing.T, expected, actual cgroups.HugetlbStats) { + if expected != actual { + logrus.Printf("Expected hugetlb stats %v but found %v\n", expected, actual) + t.Fail() + } +} + +func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) { + expectMemoryDataEquals(t, expected.Usage, actual.Usage) + expectMemoryDataEquals(t, expected.SwapUsage, actual.SwapUsage) + expectMemoryDataEquals(t, expected.KernelUsage, actual.KernelUsage) + + for key, expValue := range expected.Stats { + actValue, ok := actual.Stats[key] + if !ok { + logrus.Printf("Expected memory stat key %s not found\n", key) + t.Fail() + } + if expValue != actValue { + logrus.Printf("Expected memory stat value %d but found %d\n", expValue, actValue) + t.Fail() + } + } +} + +func expectMemoryDataEquals(t *testing.T, expected, actual cgroups.MemoryData) { + if expected.Usage != actual.Usage { + logrus.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage) + t.Fail() + } + if expected.MaxUsage != actual.MaxUsage { + logrus.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage) + t.Fail() + } + if expected.Failcnt != actual.Failcnt { + logrus.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt) + t.Fail() + } + if expected.Limit != actual.Limit { + logrus.Printf("Expected memory limit %d but found %d\n", expected.Limit, actual.Limit) + t.Fail() + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go new file mode 100644 index 0000000..7067e79 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/util_test.go @@ -0,0 +1,67 @@ +// +build linux + +/* +Utility for testing cgroup operations. + +Creates a mock of the cgroup filesystem for the duration of the test. +*/ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +type cgroupTestUtil struct { + // cgroup data to use in tests. + CgroupData *cgroupData + + // Path to the mock cgroup directory. + CgroupPath string + + // Temporary directory to store mock cgroup filesystem. + tempDir string + t *testing.T +} + +// Creates a new test util for the specified subsystem +func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { + d := &cgroupData{ + config: &configs.Cgroup{}, + } + d.config.Resources = &configs.Resources{} + tempDir, err := ioutil.TempDir("", "cgroup_test") + if err != nil { + t.Fatal(err) + } + d.root = tempDir + testCgroupPath := filepath.Join(d.root, subsystem) + if err != nil { + t.Fatal(err) + } + + // Ensure the full mock cgroup path exists. + err = os.MkdirAll(testCgroupPath, 0755) + if err != nil { + t.Fatal(err) + } + return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} +} + +func (c *cgroupTestUtil) cleanup() { + os.RemoveAll(c.tempDir) +} + +// Write the specified contents on the mock of the specified cgroup files. +func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { + for file, contents := range fileContents { + err := writeFile(c.CgroupPath, file, contents) + if err != nil { + c.t.Fatal(err) + } + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go new file mode 100644 index 0000000..99cdc18 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/fs/utils_test.go @@ -0,0 +1,97 @@ +// +build linux + +package fs + +import ( + "io/ioutil" + "math" + "os" + "path/filepath" + "strconv" + "testing" +) + +const ( + cgroupFile = "cgroup.file" + floatValue = 2048.0 + floatString = "2048" +) + +func TestGetCgroupParamsInt(t *testing.T) { + // Setup tempdir. + tempDir, err := ioutil.TempDir("", "cgroup_utils_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + tempFile := filepath.Join(tempDir, cgroupFile) + + // Success. + err = ioutil.WriteFile(tempFile, []byte(floatString), 0755) + if err != nil { + t.Fatal(err) + } + value, err := getCgroupParamUint(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %d to equal %f", value, floatValue) + } + + // Success with new line. + err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755) + if err != nil { + t.Fatal(err) + } + value, err = getCgroupParamUint(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %d to equal %f", value, floatValue) + } + + // Success with negative values + err = ioutil.WriteFile(tempFile, []byte("-12345"), 0755) + if err != nil { + t.Fatal(err) + } + value, err = getCgroupParamUint(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != 0 { + t.Fatalf("Expected %d to equal %d", value, 0) + } + + // Success with negative values lesser than min int64 + s := strconv.FormatFloat(math.MinInt64, 'f', -1, 64) + err = ioutil.WriteFile(tempFile, []byte(s), 0755) + if err != nil { + t.Fatal(err) + } + value, err = getCgroupParamUint(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != 0 { + t.Fatalf("Expected %d to equal %d", value, 0) + } + + // Not a float. + err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamUint(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } + + // Unknown file. + err = os.Remove(tempFile) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamUint(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go index bda32b2..54ace41 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/stats.go @@ -36,7 +36,9 @@ type MemoryData struct { Usage uint64 `json:"usage,omitempty"` MaxUsage uint64 `json:"max_usage,omitempty"` Failcnt uint64 `json:"failcnt"` + Limit uint64 `json:"limit"` } + type MemoryStats struct { // memory used for cache Cache uint64 `json:"cache,omitempty"` @@ -49,6 +51,11 @@ type MemoryStats struct { Stats map[string]uint64 `json:"stats,omitempty"` } +type PidsStats struct { + // number of pids in the cgroup + Current uint64 `json:"current,omitempty"` +} + type BlkioStatEntry struct { Major uint64 `json:"major,omitempty"` Minor uint64 `json:"minor,omitempty"` @@ -80,6 +87,7 @@ type HugetlbStats struct { type Stats struct { CpuStats CpuStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` + PidsStats PidsStats `json:"pids_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` // the map is in the format "size of hugepage: stats of the hugepage" HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go index fa3485f..7de9ae6 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -26,6 +26,10 @@ func (m *Manager) GetPids() ([]int, error) { return nil, fmt.Errorf("Systemd not supported") } +func (m *Manager) GetAllPids() ([]int, error) { + return nil, fmt.Errorf("Systemd not supported") +} + func (m *Manager) Destroy() error { return fmt.Errorf("Systemd not supported") } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go index 93300cd..3161639 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go @@ -55,6 +55,7 @@ var subsystems = subsystemSet{ &fs.MemoryGroup{}, &fs.CpuGroup{}, &fs.CpuacctGroup{}, + &fs.PidsGroup{}, &fs.BlkioGroup{}, &fs.HugetlbGroup{}, &fs.PerfEventGroup{}, @@ -167,6 +168,23 @@ func (m *Manager) Apply(pid int) error { properties []systemdDbus.Property ) + if c.Paths != nil { + paths := make(map[string]string) + for name, path := range c.Paths { + _, err := getSubsystemPath(m.Cgroups, name) + if err != nil { + // Don't fail if a cgroup hierarchy was not found, just skip this subsystem + if cgroups.IsNotFound(err) { + continue + } + return err + } + paths[name] = path + } + m.Paths = paths + return cgroups.EnterPid(m.Paths, pid) + } + if c.Parent != "" { slice = c.Parent } @@ -233,7 +251,7 @@ func (m *Manager) Apply(pid int) error { return err } - // we need to manually join the freezer, net_cls, net_prio and cpuset cgroup in systemd + // we need to manually join the freezer, net_cls, net_prio, pids and cpuset cgroup in systemd // because it does not currently support it via the dbus api. if err := joinFreezer(c, pid); err != nil { return err @@ -246,6 +264,10 @@ func (m *Manager) Apply(pid int) error { return err } + if err := joinPids(c, pid); err != nil { + return err + } + if err := joinCpuset(c, pid); err != nil { return err } @@ -277,17 +299,13 @@ func (m *Manager) Apply(pid int) error { paths[s.Name()] = subsystemPath } m.Paths = paths - - if paths["cpu"] != "" { - if err := fs.CheckCpushares(paths["cpu"], c.Resources.CpuShares); err != nil { - return err - } - } - return nil } func (m *Manager) Destroy() error { + if m.Cgroups.Paths != nil { + return nil + } m.mu.Lock() defer m.mu.Unlock() theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil) @@ -330,68 +348,74 @@ func join(c *configs.Cgroup, subsystem string, pid int) (string, error) { } func joinCpu(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "cpu") + _, err := join(c, "cpu", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - if c.Resources.CpuQuota != 0 { - if err = writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(c.Resources.CpuQuota, 10)); err != nil { - return err - } - } - if c.Resources.CpuPeriod != 0 { - if err = writeFile(path, "cpu.cfs_period_us", strconv.FormatInt(c.Resources.CpuPeriod, 10)); err != nil { - return err - } - } - if c.Resources.CpuRtPeriod != 0 { - if err = writeFile(path, "cpu.rt_period_us", strconv.FormatInt(c.Resources.CpuRtPeriod, 10)); err != nil { - return err - } - } - if c.Resources.CpuRtRuntime != 0 { - if err = writeFile(path, "cpu.rt_runtime_us", strconv.FormatInt(c.Resources.CpuRtRuntime, 10)); err != nil { - return err - } - } - return nil } func joinFreezer(c *configs.Cgroup, pid int) error { - path, err := join(c, "freezer", pid) + _, err := join(c, "freezer", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - freezer, err := subsystems.Get("freezer") - if err != nil { - return err - } - return freezer.Set(path, c) + return nil } func joinNetPrio(c *configs.Cgroup, pid int) error { - path, err := join(c, "net_prio", pid) + _, err := join(c, "net_prio", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - netPrio, err := subsystems.Get("net_prio") - if err != nil { - return err - } - return netPrio.Set(path, c) + return nil } func joinNetCls(c *configs.Cgroup, pid int) error { - path, err := join(c, "net_cls", pid) + _, err := join(c, "net_cls", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - netcls, err := subsystems.Get("net_cls") - if err != nil { + return nil +} + +func joinPids(c *configs.Cgroup, pid int) error { + _, err := join(c, "pids", pid) + if err != nil && !cgroups.IsNotFound(err) { return err } - return netcls.Set(path, c) + return nil +} + +// systemd represents slice heirarchy using `-`, so we need to follow suit when +// generating the path of slice. Essentially, test-a-b.slice becomes +// test.slice/test-a.slice/test-a-b.slice. +func expandSlice(slice string) (string, error) { + suffix := ".slice" + // Name has to end with ".slice", but can't be just ".slice". + if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) { + return "", fmt.Errorf("invalid slice name: %s", slice) + } + + // Path-separators are not allowed. + if strings.Contains(slice, "/") { + return "", fmt.Errorf("invalid slice name: %s", slice) + } + + var path, prefix string + sliceName := strings.TrimSuffix(slice, suffix) + for _, component := range strings.Split(sliceName, "-") { + // test--a.slice isn't permitted, nor is -test.slice. + if component == "" { + return "", fmt.Errorf("invalid slice name: %s", slice) + } + + // Append the component to the path and to the prefix. + path += prefix + component + suffix + "/" + prefix += component + "-" + } + + return path, nil } func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { @@ -410,6 +434,11 @@ func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { slice = c.Parent } + slice, err = expandSlice(slice) + if err != nil { + return "", err + } + return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil } @@ -440,6 +469,14 @@ func (m *Manager) GetPids() ([]int, error) { return cgroups.GetPids(path) } +func (m *Manager) GetAllPids() ([]int, error) { + path, err := getSubsystemPath(m.Cgroups, "devices") + if err != nil { + return nil, err + } + return cgroups.GetAllPids(path) +} + func (m *Manager) GetStats() (*cgroups.Stats, error) { m.mu.Lock() defer m.mu.Unlock() @@ -458,16 +495,23 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) { } func (m *Manager) Set(container *configs.Config) error { - for name, path := range m.Paths { - sys, err := subsystems.Get(name) - if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) { - continue + for _, sys := range subsystems { + // Get the subsystem path, but don't error out for not found cgroups. + path, err := getSubsystemPath(container.Cgroups, sys.Name()) + if err != nil && !cgroups.IsNotFound(err) { + return err } + if err := sys.Set(path, container.Cgroups); err != nil { return err } } + if m.Paths["cpu"] != "" { + if err := fs.CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil { + return err + } + } return nil } @@ -487,17 +531,13 @@ func getUnitName(c *configs.Cgroup) string { // because systemd will re-write the device settings if it needs to re-apply the cgroup context. // This happens at least for v208 when any sibling unit is started. func joinDevices(c *configs.Cgroup, pid int) error { - path, err := join(c, "devices", pid) + _, err := join(c, "devices", pid) // Even if it's `not found` error, we'll return err because devices cgroup // is hard requirement for container security. if err != nil { return err } - devices, err := subsystems.Get("devices") - if err != nil { - return err - } - return devices.Set(path, c) + return nil } func setKernelMemory(c *configs.Cgroup) error { @@ -510,52 +550,16 @@ func setKernelMemory(c *configs.Cgroup) error { return err } - if c.Resources.KernelMemory > 0 { - err = writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(c.Resources.KernelMemory, 10)) - if err != nil { - return err - } - } - - return nil + // This doesn't get called by manager.Set, so we need to do it here. + s := &fs.MemoryGroup{} + return s.SetKernelMemory(path, c) } func joinMemory(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "memory") + _, err := join(c, "memory", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - - // -1 disables memoryswap - if c.Resources.MemorySwap > 0 { - err = writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.Resources.MemorySwap, 10)) - if err != nil { - return err - } - } - if c.Resources.MemoryReservation > 0 { - err = writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Resources.MemoryReservation, 10)) - if err != nil { - return err - } - } - if c.Resources.OomKillDisable { - if err := writeFile(path, "memory.oom_control", "1"); err != nil { - return err - } - } - - if c.Resources.MemorySwappiness >= 0 && c.Resources.MemorySwappiness <= 100 { - err = writeFile(path, "memory.swappiness", strconv.FormatInt(c.Resources.MemorySwappiness, 10)) - if err != nil { - return err - } - } else if c.Resources.MemorySwappiness == -1 { - return nil - } else { - return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", c.Resources.MemorySwappiness) - } - return nil } @@ -577,68 +581,25 @@ func joinCpuset(c *configs.Cgroup, pid int) error { // expects device path instead of major minor numbers, which is also confusing // for users. So we use fs work around for now. func joinBlkio(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "blkio") + _, err := join(c, "blkio", pid) if err != nil { return err } - // systemd doesn't directly support this in the dbus properties - if c.Resources.BlkioLeafWeight != 0 { - if err := writeFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(c.Resources.BlkioLeafWeight), 10)); err != nil { - return err - } - } - for _, wd := range c.Resources.BlkioWeightDevice { - if err := writeFile(path, "blkio.weight_device", wd.WeightString()); err != nil { - return err - } - if err := writeFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil { - return err - } - } - for _, td := range c.Resources.BlkioThrottleReadBpsDevice { - if err := writeFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil { - return err - } - } - for _, td := range c.Resources.BlkioThrottleWriteBpsDevice { - if err := writeFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil { - return err - } - } - for _, td := range c.Resources.BlkioThrottleReadIOPSDevice { - if err := writeFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil { - return err - } - } - for _, td := range c.Resources.BlkioThrottleWriteIOPSDevice { - if err := writeFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil { - return err - } - } - return nil } func joinHugetlb(c *configs.Cgroup, pid int) error { - path, err := join(c, "hugetlb", pid) + _, err := join(c, "hugetlb", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - hugetlb, err := subsystems.Get("hugetlb") - if err != nil { - return err - } - return hugetlb.Set(path, c) + return nil } func joinPerfEvent(c *configs.Cgroup, pid int) error { - path, err := join(c, "perf_event", pid) + _, err := join(c, "perf_event", pid) if err != nil && !cgroups.IsNotFound(err) { return err } - perfEvent, err := subsystems.Get("perf_event") - if err != nil { - return err - } - return perfEvent.Set(path, c) + return nil } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils.go index fbdb0cb..8510c7f 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils.go @@ -5,6 +5,7 @@ package cgroups import ( "bufio" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -12,7 +13,6 @@ import ( "strings" "time" - "github.com/docker/docker/pkg/mount" "github.com/docker/go-units" ) @@ -84,10 +84,19 @@ func FindCgroupMountpointDir() (string, error) { // Safe as mountinfo encodes mountpoints with spaces as \040. index := strings.Index(text, " - ") postSeparatorFields := strings.Fields(text[index+3:]) - if len(postSeparatorFields) < 3 { - return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text) + numPostFields := len(postSeparatorFields) + + // This is an error as we can't detect if the mount is for "cgroup" + if numPostFields == 0 { + return "", fmt.Errorf("Found no fields post '-' in %q", text) } + if postSeparatorFields[0] == "cgroup" { + // Check that the mount is properly formated. + if numPostFields < 3 { + return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text) + } + return filepath.Dir(fields[4]), nil } } @@ -112,11 +121,45 @@ func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) { return getControllerPath(m.Subsystems[0], cgroups) } +func getCgroupMountsHelper(ss map[string]bool, mi io.Reader) ([]Mount, error) { + res := make([]Mount, 0, len(ss)) + scanner := bufio.NewScanner(mi) + for scanner.Scan() { + txt := scanner.Text() + sepIdx := strings.IndexByte(txt, '-') + if sepIdx == -1 { + return nil, fmt.Errorf("invalid mountinfo format") + } + if txt[sepIdx+2:sepIdx+8] != "cgroup" { + continue + } + fields := strings.Split(txt, " ") + m := Mount{ + Mountpoint: fields[4], + Root: fields[3], + } + for _, opt := range strings.Split(fields[len(fields)-1], ",") { + if strings.HasPrefix(opt, cgroupNamePrefix) { + m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):]) + } + if ss[opt] { + m.Subsystems = append(m.Subsystems, opt) + } + } + res = append(res, m) + } + if err := scanner.Err(); err != nil { + return nil, err + } + return res, nil +} + func GetCgroupMounts() ([]Mount, error) { - mounts, err := mount.GetMounts() + f, err := os.Open("/proc/self/mountinfo") if err != nil { return nil, err } + defer f.Close() all, err := GetAllSubsystems() if err != nil { @@ -127,24 +170,7 @@ func GetCgroupMounts() ([]Mount, error) { for _, s := range all { allMap[s] = true } - - res := []Mount{} - for _, mount := range mounts { - if mount.Fstype == "cgroup" { - m := Mount{Mountpoint: mount.Mountpoint, Root: mount.Root} - - for _, opt := range strings.Split(mount.VfsOpts, ",") { - if strings.HasPrefix(opt, cgroupNamePrefix) { - m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):]) - } - if allMap[opt] { - m.Subsystems = append(m.Subsystems, opt) - } - } - res = append(res, m) - } - } - return res, nil + return getCgroupMountsHelper(allMap, f) } // Returns all the cgroup subsystems supported by the kernel @@ -323,9 +349,14 @@ func GetHugePageSize() ([]string, error) { return pageSizes, nil } -// GetPids returns all pids, that were added to cgroup at path and to all its -// subcgroups. +// GetPids returns all pids, that were added to cgroup at path. func GetPids(path string) ([]int, error) { + return readProcsFile(path) +} + +// GetAllPids returns all pids, that were added to cgroup at path and to all its +// subcgroups. +func GetAllPids(path string) ([]int, error) { var pids []int // collect pids from all sub-cgroups err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error { diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils_test.go new file mode 100644 index 0000000..179c14a --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/cgroups/utils_test.go @@ -0,0 +1,138 @@ +package cgroups + +import ( + "bytes" + "strings" + "testing" +) + +const fedoraMountinfo = `15 35 0:3 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw +16 35 0:14 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw,seclabel +17 35 0:5 / /dev rw,nosuid shared:2 - devtmpfs devtmpfs rw,seclabel,size=8056484k,nr_inodes=2014121,mode=755 +18 16 0:15 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:7 - securityfs securityfs rw +19 16 0:13 / /sys/fs/selinux rw,relatime shared:8 - selinuxfs selinuxfs rw +20 17 0:16 / /dev/shm rw,nosuid,nodev shared:3 - tmpfs tmpfs rw,seclabel +21 17 0:10 / /dev/pts rw,nosuid,noexec,relatime shared:4 - devpts devpts rw,seclabel,gid=5,mode=620,ptmxmode=000 +22 35 0:17 / /run rw,nosuid,nodev shared:21 - tmpfs tmpfs rw,seclabel,mode=755 +23 16 0:18 / /sys/fs/cgroup rw,nosuid,nodev,noexec shared:9 - tmpfs tmpfs rw,seclabel,mode=755 +24 23 0:19 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd +25 16 0:20 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:20 - pstore pstore rw +26 23 0:21 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,cpuset,clone_children +27 23 0:22 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:12 - cgroup cgroup rw,cpuacct,cpu,clone_children +28 23 0:23 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,memory,clone_children +29 23 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,devices,clone_children +30 23 0:25 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,freezer,clone_children +31 23 0:26 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,net_cls,clone_children +32 23 0:27 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,blkio,clone_children +33 23 0:28 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,perf_event,clone_children +34 23 0:29 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,hugetlb,clone_children +35 1 253:2 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root--f20 rw,seclabel,data=ordered +36 15 0:30 / /proc/sys/fs/binfmt_misc rw,relatime shared:22 - autofs systemd-1 rw,fd=38,pgrp=1,timeout=300,minproto=5,maxproto=5,direct +37 17 0:12 / /dev/mqueue rw,relatime shared:23 - mqueue mqueue rw,seclabel +38 35 0:31 / /tmp rw shared:24 - tmpfs tmpfs rw,seclabel +39 17 0:32 / /dev/hugepages rw,relatime shared:25 - hugetlbfs hugetlbfs rw,seclabel +40 16 0:7 / /sys/kernel/debug rw,relatime shared:26 - debugfs debugfs rw +41 16 0:33 / /sys/kernel/config rw,relatime shared:27 - configfs configfs rw +42 35 0:34 / /var/lib/nfs/rpc_pipefs rw,relatime shared:28 - rpc_pipefs sunrpc rw +43 15 0:35 / /proc/fs/nfsd rw,relatime shared:29 - nfsd sunrpc rw +45 35 8:17 / /boot rw,relatime shared:30 - ext4 /dev/sdb1 rw,seclabel,data=ordered +46 35 253:4 / /home rw,relatime shared:31 - ext4 /dev/mapper/ssd-home rw,seclabel,data=ordered +47 35 253:5 / /var/lib/libvirt/images rw,noatime,nodiratime shared:32 - ext4 /dev/mapper/ssd-virt rw,seclabel,discard,data=ordered +48 35 253:12 / /mnt/old rw,relatime shared:33 - ext4 /dev/mapper/HelpDeskRHEL6-FedoraRoot rw,seclabel,data=ordered +121 22 0:36 / /run/user/1000/gvfs rw,nosuid,nodev,relatime shared:104 - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000 +124 16 0:37 / /sys/fs/fuse/connections rw,relatime shared:107 - fusectl fusectl rw +165 38 253:3 / /tmp/mnt rw,relatime shared:147 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered +167 35 253:15 / /var/lib/docker/devicemapper/mnt/aae4076022f0e2b80a2afbf8fc6df450c52080191fcef7fb679a73e6f073e5c2 rw,relatime shared:149 - ext4 /dev/mapper/docker-253:2-425882-aae4076022f0e2b80a2afbf8fc6df450c52080191fcef7fb679a73e6f073e5c2 rw,seclabel,discard,stripe=16,data=ordered +171 35 253:16 / /var/lib/docker/devicemapper/mnt/c71be651f114db95180e472f7871b74fa597ee70a58ccc35cb87139ddea15373 rw,relatime shared:153 - ext4 /dev/mapper/docker-253:2-425882-c71be651f114db95180e472f7871b74fa597ee70a58ccc35cb87139ddea15373 rw,seclabel,discard,stripe=16,data=ordered +175 35 253:17 / /var/lib/docker/devicemapper/mnt/1bac6ab72862d2d5626560df6197cf12036b82e258c53d981fa29adce6f06c3c rw,relatime shared:157 - ext4 /dev/mapper/docker-253:2-425882-1bac6ab72862d2d5626560df6197cf12036b82e258c53d981fa29adce6f06c3c rw,seclabel,discard,stripe=16,data=ordered +179 35 253:18 / /var/lib/docker/devicemapper/mnt/d710a357d77158e80d5b2c55710ae07c94e76d34d21ee7bae65ce5418f739b09 rw,relatime shared:161 - ext4 /dev/mapper/docker-253:2-425882-d710a357d77158e80d5b2c55710ae07c94e76d34d21ee7bae65ce5418f739b09 rw,seclabel,discard,stripe=16,data=ordered +183 35 253:19 / /var/lib/docker/devicemapper/mnt/6479f52366114d5f518db6837254baab48fab39f2ac38d5099250e9a6ceae6c7 rw,relatime shared:165 - ext4 /dev/mapper/docker-253:2-425882-6479f52366114d5f518db6837254baab48fab39f2ac38d5099250e9a6ceae6c7 rw,seclabel,discard,stripe=16,data=ordered +187 35 253:20 / /var/lib/docker/devicemapper/mnt/8d9df91c4cca5aef49eeb2725292aab324646f723a7feab56be34c2ad08268e1 rw,relatime shared:169 - ext4 /dev/mapper/docker-253:2-425882-8d9df91c4cca5aef49eeb2725292aab324646f723a7feab56be34c2ad08268e1 rw,seclabel,discard,stripe=16,data=ordered +191 35 253:21 / /var/lib/docker/devicemapper/mnt/c8240b768603d32e920d365dc9d1dc2a6af46cd23e7ae819947f969e1b4ec661 rw,relatime shared:173 - ext4 /dev/mapper/docker-253:2-425882-c8240b768603d32e920d365dc9d1dc2a6af46cd23e7ae819947f969e1b4ec661 rw,seclabel,discard,stripe=16,data=ordered +195 35 253:22 / /var/lib/docker/devicemapper/mnt/2eb3a01278380bbf3ed12d86ac629eaa70a4351301ee307a5cabe7b5f3b1615f rw,relatime shared:177 - ext4 /dev/mapper/docker-253:2-425882-2eb3a01278380bbf3ed12d86ac629eaa70a4351301ee307a5cabe7b5f3b1615f rw,seclabel,discard,stripe=16,data=ordered +199 35 253:23 / /var/lib/docker/devicemapper/mnt/37a17fb7c9d9b80821235d5f2662879bd3483915f245f9b49cdaa0e38779b70b rw,relatime shared:181 - ext4 /dev/mapper/docker-253:2-425882-37a17fb7c9d9b80821235d5f2662879bd3483915f245f9b49cdaa0e38779b70b rw,seclabel,discard,stripe=16,data=ordered +203 35 253:24 / /var/lib/docker/devicemapper/mnt/aea459ae930bf1de913e2f29428fd80ee678a1e962d4080019d9f9774331ee2b rw,relatime shared:185 - ext4 /dev/mapper/docker-253:2-425882-aea459ae930bf1de913e2f29428fd80ee678a1e962d4080019d9f9774331ee2b rw,seclabel,discard,stripe=16,data=ordered +207 35 253:25 / /var/lib/docker/devicemapper/mnt/928ead0bc06c454bd9f269e8585aeae0a6bd697f46dc8754c2a91309bc810882 rw,relatime shared:189 - ext4 /dev/mapper/docker-253:2-425882-928ead0bc06c454bd9f269e8585aeae0a6bd697f46dc8754c2a91309bc810882 rw,seclabel,discard,stripe=16,data=ordered +211 35 253:26 / /var/lib/docker/devicemapper/mnt/0f284d18481d671644706e7a7244cbcf63d590d634cc882cb8721821929d0420 rw,relatime shared:193 - ext4 /dev/mapper/docker-253:2-425882-0f284d18481d671644706e7a7244cbcf63d590d634cc882cb8721821929d0420 rw,seclabel,discard,stripe=16,data=ordered +215 35 253:27 / /var/lib/docker/devicemapper/mnt/d9dd16722ab34c38db2733e23f69e8f4803ce59658250dd63e98adff95d04919 rw,relatime shared:197 - ext4 /dev/mapper/docker-253:2-425882-d9dd16722ab34c38db2733e23f69e8f4803ce59658250dd63e98adff95d04919 rw,seclabel,discard,stripe=16,data=ordered +219 35 253:28 / /var/lib/docker/devicemapper/mnt/bc4500479f18c2c08c21ad5282e5f826a016a386177d9874c2764751c031d634 rw,relatime shared:201 - ext4 /dev/mapper/docker-253:2-425882-bc4500479f18c2c08c21ad5282e5f826a016a386177d9874c2764751c031d634 rw,seclabel,discard,stripe=16,data=ordered +223 35 253:29 / /var/lib/docker/devicemapper/mnt/7770c8b24eb3d5cc159a065910076938910d307ab2f5d94e1dc3b24c06ee2c8a rw,relatime shared:205 - ext4 /dev/mapper/docker-253:2-425882-7770c8b24eb3d5cc159a065910076938910d307ab2f5d94e1dc3b24c06ee2c8a rw,seclabel,discard,stripe=16,data=ordered +227 35 253:30 / /var/lib/docker/devicemapper/mnt/c280cd3d0bf0aa36b478b292279671624cceafc1a67eaa920fa1082601297adf rw,relatime shared:209 - ext4 /dev/mapper/docker-253:2-425882-c280cd3d0bf0aa36b478b292279671624cceafc1a67eaa920fa1082601297adf rw,seclabel,discard,stripe=16,data=ordered +231 35 253:31 / /var/lib/docker/devicemapper/mnt/8b59a7d9340279f09fea67fd6ad89ddef711e9e7050eb647984f8b5ef006335f rw,relatime shared:213 - ext4 /dev/mapper/docker-253:2-425882-8b59a7d9340279f09fea67fd6ad89ddef711e9e7050eb647984f8b5ef006335f rw,seclabel,discard,stripe=16,data=ordered +235 35 253:32 / /var/lib/docker/devicemapper/mnt/1a28059f29eda821578b1bb27a60cc71f76f846a551abefabce6efd0146dce9f rw,relatime shared:217 - ext4 /dev/mapper/docker-253:2-425882-1a28059f29eda821578b1bb27a60cc71f76f846a551abefabce6efd0146dce9f rw,seclabel,discard,stripe=16,data=ordered +239 35 253:33 / /var/lib/docker/devicemapper/mnt/e9aa60c60128cad1 rw,relatime shared:221 - ext4 /dev/mapper/docker-253:2-425882-e9aa60c60128cad1 rw,seclabel,discard,stripe=16,data=ordered +243 35 253:34 / /var/lib/docker/devicemapper/mnt/5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d-init rw,relatime shared:225 - ext4 /dev/mapper/docker-253:2-425882-5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d-init rw,seclabel,discard,stripe=16,data=ordered +247 35 253:35 / /var/lib/docker/devicemapper/mnt/5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d rw,relatime shared:229 - ext4 /dev/mapper/docker-253:2-425882-5fec11304b6f4713fea7b6ccdcc1adc0a1966187f590fe25a8227428a8df275d rw,seclabel,discard,stripe=16,data=ordered +31 21 0:23 / /DATA/foo_bla_bla rw,relatime - cifs //foo/BLA\040BLA\040BLA/ rw,sec=ntlm,cache=loose,unc=\\foo\BLA BLA BLA,username=my_login,domain=mydomain.com,uid=12345678,forceuid,gid=12345678,forcegid,addr=10.1.30.10,file_mode=0755,dir_mode=0755,nounix,rsize=61440,wsize=65536,actimeo=1` + +func TestGetCgroupMounts(t *testing.T) { + subsystems := map[string]bool{ + "cpuset": true, + "cpu": true, + "cpuacct": true, + "memory": true, + "devices": true, + "freezer": true, + "net_cls": true, + "blkio": true, + "perf_event": true, + "hugetlb": true, + } + mi := bytes.NewBufferString(fedoraMountinfo) + cgMounts, err := getCgroupMountsHelper(subsystems, mi) + if err != nil { + t.Fatal(err) + } + cgMap := make(map[string]Mount) + for _, m := range cgMounts { + for _, ss := range m.Subsystems { + cgMap[ss] = m + } + } + for ss := range subsystems { + m, ok := cgMap[ss] + if !ok { + t.Fatalf("%s not found", ss) + } + if m.Root != "/" { + t.Fatalf("unexpected root for %s: %s", ss, m.Root) + } + if !strings.HasPrefix(m.Mountpoint, "/sys/fs/cgroup/") && !strings.Contains(m.Mountpoint, ss) { + t.Fatalf("unexpected mountpoint for %s: %s", ss, m.Mountpoint) + } + var ssFound bool + for _, mss := range m.Subsystems { + if mss == ss { + ssFound = true + break + } + } + if !ssFound { + t.Fatalf("subsystem %s not found in Subsystems field %v", ss, m.Subsystems) + } + } +} + +func BenchmarkGetCgroupMounts(b *testing.B) { + subsystems := map[string]bool{ + "cpuset": true, + "cpu": true, + "cpuacct": true, + "memory": true, + "devices": true, + "freezer": true, + "net_cls": true, + "blkio": true, + "perf_event": true, + "hugetlb": true, + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + mi := bytes.NewBufferString(fedoraMountinfo) + b.StartTimer() + if _, err := getCgroupMountsHelper(subsystems, mi); err != nil { + b.Fatal(err) + } + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go index ef78132..40a033f 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go @@ -11,25 +11,38 @@ const ( ) type Cgroup struct { - Name string `json:"name"` + // Deprecated, use Path instead + Name string `json:"name,omitempty"` - // name of parent cgroup or slice - Parent string `json:"parent"` + // name of parent of cgroup or slice + // Deprecated, use Path instead + Parent string `json:"parent,omitempty"` + + // Path specifies the path to cgroups that are created and/or joined by the container. + // The path is assumed to be relative to the host system cgroup mountpoint. + Path string `json:"path"` // ScopePrefix decribes prefix for the scope name ScopePrefix string `json:"scope_prefix"` + // Paths represent the absolute cgroups paths to join. + // This takes precedence over Path. + Paths map[string]string + // Resources contains various cgroups settings to apply - Resources *Resources `json:"resources"` + *Resources } type Resources struct { // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. - AllowAllDevices bool `json:"allow_all_devices"` + // Deprecated + AllowAllDevices bool `json:"allow_all_devices,omitempty"` + // Deprecated + AllowedDevices []*Device `json:"allowed_devices,omitempty"` + // Deprecated + DeniedDevices []*Device `json:"denied_devices,omitempty"` - AllowedDevices []*Device `json:"allowed_devices"` - - DeniedDevices []*Device `json:"denied_devices"` + Devices []*Device `json:"devices"` // Memory limit (in bytes) Memory int64 `json:"memory"` @@ -37,7 +50,7 @@ type Resources struct { // Memory reservation or soft_limit (in bytes) MemoryReservation int64 `json:"memory_reservation"` - // Total memory usage (memory + swap); set `-1' to disable swap + // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwap int64 `json:"memory_swap"` // Kernel memory limit (in bytes) @@ -64,6 +77,9 @@ type Resources struct { // MEM to use CpusetMems string `json:"cpuset_mems"` + // Process limit; set <= `0' to disable limit. + PidsLimit int64 `json:"pids_limit"` + // Specifies per cgroup weight, range is from 10 to 1000. BlkioWeight uint16 `json:"blkio_weight"` diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config_unix_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config_unix_test.go new file mode 100644 index 0000000..27d07d4 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config_unix_test.go @@ -0,0 +1,156 @@ +// +build linux freebsd + +package configs + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" +) + +// Checks whether the expected capability is specified in the capabilities. +func contains(expected string, values []string) bool { + for _, v := range values { + if v == expected { + return true + } + } + return false +} + +func containsDevice(expected *Device, values []*Device) bool { + for _, d := range values { + if d.Path == expected.Path && + d.Permissions == expected.Permissions && + d.FileMode == expected.FileMode && + d.Major == expected.Major && + d.Minor == expected.Minor && + d.Type == expected.Type { + return true + } + } + return false +} + +func loadConfig(name string) (*Config, error) { + f, err := os.Open(filepath.Join("../sample_configs", name)) + if err != nil { + return nil, err + } + defer f.Close() + + var container *Config + if err := json.NewDecoder(f).Decode(&container); err != nil { + return nil, err + } + + // Check that a config doesn't contain extra fields + var configMap, abstractMap map[string]interface{} + + if _, err := f.Seek(0, 0); err != nil { + return nil, err + } + + if err := json.NewDecoder(f).Decode(&abstractMap); err != nil { + return nil, err + } + + configData, err := json.Marshal(&container) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(configData, &configMap); err != nil { + return nil, err + } + + for k := range configMap { + delete(abstractMap, k) + } + + if len(abstractMap) != 0 { + return nil, fmt.Errorf("unknown fields: %s", abstractMap) + } + + return container, nil +} + +func TestRemoveNamespace(t *testing.T) { + ns := Namespaces{ + {Type: NEWNET}, + } + if !ns.Remove(NEWNET) { + t.Fatal("NEWNET was not removed") + } + if len(ns) != 0 { + t.Fatalf("namespaces should have 0 items but reports %d", len(ns)) + } +} + +func TestHostUIDNoUSERNS(t *testing.T) { + config := &Config{ + Namespaces: Namespaces{}, + } + uid, err := config.HostUID() + if err != nil { + t.Fatal(err) + } + if uid != 0 { + t.Fatalf("expected uid 0 with no USERNS but received %d", uid) + } +} + +func TestHostUIDWithUSERNS(t *testing.T) { + config := &Config{ + Namespaces: Namespaces{{Type: NEWUSER}}, + UidMappings: []IDMap{ + { + ContainerID: 0, + HostID: 1000, + Size: 1, + }, + }, + } + uid, err := config.HostUID() + if err != nil { + t.Fatal(err) + } + if uid != 1000 { + t.Fatalf("expected uid 1000 with no USERNS but received %d", uid) + } +} + +func TestHostGIDNoUSERNS(t *testing.T) { + config := &Config{ + Namespaces: Namespaces{}, + } + uid, err := config.HostGID() + if err != nil { + t.Fatal(err) + } + if uid != 0 { + t.Fatalf("expected gid 0 with no USERNS but received %d", uid) + } +} + +func TestHostGIDWithUSERNS(t *testing.T) { + config := &Config{ + Namespaces: Namespaces{{Type: NEWUSER}}, + GidMappings: []IDMap{ + { + ContainerID: 0, + HostID: 1000, + Size: 1, + }, + }, + } + uid, err := config.HostGID() + if err != nil { + t.Fatal(err) + } + if uid != 1000 { + t.Fatalf("expected gid 1000 with no USERNS but received %d", uid) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config_windows_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config_windows_test.go new file mode 100644 index 0000000..1a0c8fa --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/config_windows_test.go @@ -0,0 +1,3 @@ +package configs + +// All current tests are for Unix-specific functionality diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device.go b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device.go index a52a024..8701bb2 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device.go @@ -35,6 +35,9 @@ type Device struct { // Gid of the device. Gid uint32 `json:"gid"` + + // Write the file to the allowed list + Allow bool `json:"allow"` } func (d *Device) CgroupString() string { diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go index 0ce040f..e452992 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go @@ -82,20 +82,6 @@ var ( Minor: 1, Permissions: "rwm", }, - { - Path: "/dev/tty0", - Type: 'c', - Major: 4, - Minor: 0, - Permissions: "rwm", - }, - { - Path: "/dev/tty1", - Type: 'c', - Major: 4, - Minor: 1, - Permissions: "rwm", - }, // /dev/pts/ - pts namespaces are "coming soon" { Path: "", diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/container.go b/vendor/src/github.com/opencontainers/runc/libcontainer/container.go index 051c8cf..6829123 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/container.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/container.go @@ -6,6 +6,7 @@ package libcontainer import ( "os" + "time" "github.com/opencontainers/runc/libcontainer/configs" ) @@ -14,8 +15,11 @@ import ( type Status int const ( + // The container exists but has not been run yet + Created Status = iota + // The container exists and is running. - Running Status = iota + 1 + Running // The container exists, it is in the process of being paused. Pausing @@ -32,6 +36,8 @@ const ( func (s Status) String() string { switch s { + case Created: + return "created" case Running: return "running" case Pausing: @@ -43,7 +49,7 @@ func (s Status) String() string { case Destroyed: return "destroyed" default: - return "undefined" + return "unknown" } } @@ -56,9 +62,12 @@ type BaseState struct { // InitProcessPid is the init process id in the parent namespace. InitProcessPid int `json:"init_process_pid"` - // InitProcessStartTime is the init process start time. + // InitProcessStartTime is the init process start time in clock cycles since boot time. InitProcessStartTime string `json:"init_process_start"` + // Created is the unix timestamp for the creation time of the container in UTC + Created time.Time `json:"created"` + // Config is the container's configuration. Config configs.Config `json:"config"` } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go index 62b228a..284e15e 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux.go @@ -15,12 +15,14 @@ import ( "strings" "sync" "syscall" + "time" "github.com/Sirupsen/logrus" "github.com/golang/protobuf/proto" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/criurpc" + "github.com/opencontainers/runc/libcontainer/utils" "github.com/vishvananda/netlink/nl" ) @@ -38,6 +40,7 @@ type linuxContainer struct { m sync.Mutex criuVersion int state containerState + created time.Time } // State represents a running container's state @@ -104,6 +107,12 @@ type Container interface { // errors: // Systemerror - System error. NotifyOOM() (<-chan struct{}, error) + + // NotifyMemoryPressure returns a read-only channel signaling when the container reaches a given pressure level + // + // errors: + // Systemerror - System error. + NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) } // ID returns the container's unique ID @@ -129,7 +138,7 @@ func (c *linuxContainer) State() (*State, error) { } func (c *linuxContainer) Processes() ([]int, error) { - pids, err := c.cgroupManager.GetPids() + pids, err := c.cgroupManager.GetAllPids() if err != nil { return nil, newSystemError(err) } @@ -183,29 +192,30 @@ func (c *linuxContainer) Start(process *Process) error { } return newSystemError(err) } + // generate a timestamp indicating when the container was started + c.created = time.Now().UTC() + + c.state = &runningState{ + c: c, + } if doInit { if err := c.updateState(parent); err != nil { return err } - } else { - c.state.transition(&nullState{ - c: c, - s: Running, - }) - } - if c.config.Hooks != nil { - s := configs.HookState{ - Version: c.config.Version, - ID: c.id, - Pid: parent.pid(), - Root: c.config.Rootfs, - } - for _, hook := range c.config.Hooks.Poststart { - if err := hook.Run(s); err != nil { - if err := parent.terminate(); err != nil { - logrus.Warn(err) + if c.config.Hooks != nil { + s := configs.HookState{ + Version: c.config.Version, + ID: c.id, + Pid: parent.pid(), + Root: c.config.Rootfs, + } + for _, hook := range c.config.Hooks.Poststart { + if err := hook.Run(s); err != nil { + if err := parent.terminate(); err != nil { + logrus.Warn(err) + } + return newSystemError(err) } - return newSystemError(err) } } } @@ -258,7 +268,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. } func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { - t := "_LIBCONTAINER_INITTYPE=standard" + t := "_LIBCONTAINER_INITTYPE=" + string(initStandard) cloneFlags := c.config.Namespaces.CloneFlags() if cloneFlags&syscall.CLONE_NEWUSER != 0 { if err := c.addUidGidMappings(cmd.SysProcAttr); err != nil { @@ -285,7 +295,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c } func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) { - cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE=setns") + cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns)) // for setns process, we dont have to set cloneflags as the process namespaces // will only be set via setns syscall data, err := c.bootstrapData(0, c.initProcess.pid(), p.consolePath) @@ -334,6 +344,13 @@ func (c *linuxContainer) Destroy() error { func (c *linuxContainer) Pause() error { c.m.Lock() defer c.m.Unlock() + status, err := c.currentStatus() + if err != nil { + return err + } + if status != Running { + return newGenericError(fmt.Errorf("container not running"), ContainerNotRunning) + } if err := c.cgroupManager.Freeze(configs.Frozen); err != nil { return err } @@ -345,6 +362,13 @@ func (c *linuxContainer) Pause() error { func (c *linuxContainer) Resume() error { c.m.Lock() defer c.m.Unlock() + status, err := c.currentStatus() + if err != nil { + return err + } + if status != Paused { + return newGenericError(fmt.Errorf("container not paused"), ContainerNotPaused) + } if err := c.cgroupManager.Freeze(configs.Thawed); err != nil { return err } @@ -357,6 +381,10 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) { return notifyOnOOM(c.cgroupManager.GetPaths()) } +func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) { + return notifyMemoryPressure(c.cgroupManager.GetPaths(), level) +} + // XXX debug support, remove when debugging done. func addArgsFromEnv(evar string, args *[]string) { if e := os.Getenv(evar); e != "" { @@ -929,9 +957,6 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc func (c *linuxContainer) updateState(process parentProcess) error { c.initProcess = process - if err := c.refreshState(); err != nil { - return err - } state, err := c.currentState() if err != nil { return err @@ -945,7 +970,7 @@ func (c *linuxContainer) saveState(s *State) error { return err } defer f.Close() - return json.NewEncoder(f).Encode(s) + return utils.WriteJSON(f, s) } func (c *linuxContainer) deleteState() error { @@ -1007,35 +1032,37 @@ func (c *linuxContainer) isPaused() (bool, error) { } func (c *linuxContainer) currentState() (*State, error) { - status, err := c.currentStatus() - if err != nil { - return nil, err - } - if status == Destroyed { - return nil, newGenericError(fmt.Errorf("container destroyed"), ContainerNotExists) - } - startTime, err := c.initProcess.startTime() - if err != nil { - return nil, newSystemError(err) + var ( + startTime string + externalDescriptors []string + pid = -1 + ) + if c.initProcess != nil { + pid = c.initProcess.pid() + startTime, _ = c.initProcess.startTime() + externalDescriptors = c.initProcess.externalDescriptors() } state := &State{ BaseState: BaseState{ ID: c.ID(), Config: *c.config, - InitProcessPid: c.initProcess.pid(), + InitProcessPid: pid, InitProcessStartTime: startTime, + Created: c.created, }, CgroupPaths: c.cgroupManager.GetPaths(), NamespacePaths: make(map[configs.NamespaceType]string), - ExternalDescriptors: c.initProcess.externalDescriptors(), + ExternalDescriptors: externalDescriptors, } - for _, ns := range c.config.Namespaces { - state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) - } - for _, nsType := range configs.NamespaceTypes() { - if _, ok := state.NamespacePaths[nsType]; !ok { - ns := configs.Namespace{Type: nsType} - state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) + if pid > 0 { + for _, ns := range c.config.Namespaces { + state.NamespacePaths[ns.Type] = ns.GetPath(pid) + } + for _, nsType := range configs.NamespaceTypes() { + if _, ok := state.NamespacePaths[nsType]; !ok { + ns := configs.Namespace{Type: nsType} + state.NamespacePaths[ns.Type] = ns.GetPath(pid) + } } } return state, nil diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux_test.go new file mode 100644 index 0000000..3af30bc --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/container_linux_test.go @@ -0,0 +1,218 @@ +// +build linux + +package libcontainer + +import ( + "fmt" + "os" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" +) + +type mockCgroupManager struct { + pids []int + allPids []int + stats *cgroups.Stats + paths map[string]string +} + +func (m *mockCgroupManager) GetPids() ([]int, error) { + return m.pids, nil +} + +func (m *mockCgroupManager) GetAllPids() ([]int, error) { + return m.allPids, nil +} + +func (m *mockCgroupManager) GetStats() (*cgroups.Stats, error) { + return m.stats, nil +} + +func (m *mockCgroupManager) Apply(pid int) error { + return nil +} + +func (m *mockCgroupManager) Set(container *configs.Config) error { + return nil +} + +func (m *mockCgroupManager) Destroy() error { + return nil +} + +func (m *mockCgroupManager) GetPaths() map[string]string { + return m.paths +} + +func (m *mockCgroupManager) Freeze(state configs.FreezerState) error { + return nil +} + +type mockProcess struct { + _pid int + started string +} + +func (m *mockProcess) terminate() error { + return nil +} + +func (m *mockProcess) pid() int { + return m._pid +} + +func (m *mockProcess) startTime() (string, error) { + return m.started, nil +} + +func (m *mockProcess) start() error { + return nil +} + +func (m *mockProcess) wait() (*os.ProcessState, error) { + return nil, nil +} + +func (m *mockProcess) signal(_ os.Signal) error { + return nil +} + +func (p *mockProcess) externalDescriptors() []string { + return []string{} +} + +func (p *mockProcess) setExternalDescriptors(newFds []string) { +} + +func TestGetContainerPids(t *testing.T) { + container := &linuxContainer{ + id: "myid", + config: &configs.Config{}, + cgroupManager: &mockCgroupManager{allPids: []int{1, 2, 3}}, + } + pids, err := container.Processes() + if err != nil { + t.Fatal(err) + } + for i, expected := range []int{1, 2, 3} { + if pids[i] != expected { + t.Fatalf("expected pid %d but received %d", expected, pids[i]) + } + } +} + +func TestGetContainerStats(t *testing.T) { + container := &linuxContainer{ + id: "myid", + config: &configs.Config{}, + cgroupManager: &mockCgroupManager{ + pids: []int{1, 2, 3}, + stats: &cgroups.Stats{ + MemoryStats: cgroups.MemoryStats{ + Usage: cgroups.MemoryData{ + Usage: 1024, + }, + }, + }, + }, + } + stats, err := container.Stats() + if err != nil { + t.Fatal(err) + } + if stats.CgroupStats == nil { + t.Fatal("cgroup stats are nil") + } + if stats.CgroupStats.MemoryStats.Usage.Usage != 1024 { + t.Fatalf("expected memory usage 1024 but recevied %d", stats.CgroupStats.MemoryStats.Usage.Usage) + } +} + +func TestGetContainerState(t *testing.T) { + var ( + pid = os.Getpid() + expectedMemoryPath = "/sys/fs/cgroup/memory/myid" + expectedNetworkPath = "/networks/fd" + ) + container := &linuxContainer{ + id: "myid", + config: &configs.Config{ + Namespaces: []configs.Namespace{ + {Type: configs.NEWPID}, + {Type: configs.NEWNS}, + {Type: configs.NEWNET, Path: expectedNetworkPath}, + {Type: configs.NEWUTS}, + // emulate host for IPC + //{Type: configs.NEWIPC}, + }, + }, + initProcess: &mockProcess{ + _pid: pid, + started: "010", + }, + cgroupManager: &mockCgroupManager{ + pids: []int{1, 2, 3}, + stats: &cgroups.Stats{ + MemoryStats: cgroups.MemoryStats{ + Usage: cgroups.MemoryData{ + Usage: 1024, + }, + }, + }, + paths: map[string]string{ + "memory": expectedMemoryPath, + }, + }, + } + container.state = &createdState{c: container} + state, err := container.State() + if err != nil { + t.Fatal(err) + } + if state.InitProcessPid != pid { + t.Fatalf("expected pid %d but received %d", pid, state.InitProcessPid) + } + if state.InitProcessStartTime != "010" { + t.Fatalf("expected process start time 010 but received %s", state.InitProcessStartTime) + } + paths := state.CgroupPaths + if paths == nil { + t.Fatal("cgroup paths should not be nil") + } + if memPath := paths["memory"]; memPath != expectedMemoryPath { + t.Fatalf("expected memory path %q but received %q", expectedMemoryPath, memPath) + } + for _, ns := range container.config.Namespaces { + path := state.NamespacePaths[ns.Type] + if path == "" { + t.Fatalf("expected non nil namespace path for %s", ns.Type) + } + if ns.Type == configs.NEWNET { + if path != expectedNetworkPath { + t.Fatalf("expected path %q but received %q", expectedNetworkPath, path) + } + } else { + file := "" + switch ns.Type { + case configs.NEWNET: + file = "net" + case configs.NEWNS: + file = "mnt" + case configs.NEWPID: + file = "pid" + case configs.NEWIPC: + file = "ipc" + case configs.NEWUSER: + file = "user" + case configs.NEWUTS: + file = "uts" + } + expected := fmt.Sprintf("/proc/%d/ns/%s", pid, file) + if expected != path { + t.Fatalf("expected path %q but received %q", expected, path) + } + } + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_test.go new file mode 100644 index 0000000..50ea78b --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_test.go @@ -0,0 +1,63 @@ +// +build linux freebsd + +package devices + +import ( + "errors" + "os" + "testing" +) + +func TestDeviceFromPathLstatFailure(t *testing.T) { + testError := errors.New("test error") + + // Override os.Lstat to inject error. + osLstat = func(path string) (os.FileInfo, error) { + return nil, testError + } + + _, err := DeviceFromPath("", "") + if err != testError { + t.Fatalf("Unexpected error %v, expected %v", err, testError) + } +} + +func TestHostDevicesIoutilReadDirFailure(t *testing.T) { + testError := errors.New("test error") + + // Override ioutil.ReadDir to inject error. + ioutilReadDir = func(dirname string) ([]os.FileInfo, error) { + return nil, testError + } + + _, err := HostDevices() + if err != testError { + t.Fatalf("Unexpected error %v, expected %v", err, testError) + } +} + +func TestHostDevicesIoutilReadDirDeepFailure(t *testing.T) { + testError := errors.New("test error") + called := false + + // Override ioutil.ReadDir to inject error after the first call. + ioutilReadDir = func(dirname string) ([]os.FileInfo, error) { + if called { + return nil, testError + } + called = true + + // Provoke a second call. + fi, err := os.Lstat("/tmp") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + return []os.FileInfo{fi}, nil + } + + _, err := HostDevices() + if err != testError { + t.Fatalf("Unexpected error %v, expected %v", err, testError) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_unix.go b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_unix.go new file mode 100644 index 0000000..c02b73e --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_unix.go @@ -0,0 +1,102 @@ +// +build linux freebsd + +package devices + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "syscall" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +var ( + ErrNotADevice = errors.New("not a device node") +) + +// Testing dependencies +var ( + osLstat = os.Lstat + ioutilReadDir = ioutil.ReadDir +) + +// Given the path to a device and it's cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct. +func DeviceFromPath(path, permissions string) (*configs.Device, error) { + fileInfo, err := osLstat(path) + if err != nil { + return nil, err + } + var ( + devType rune + mode = fileInfo.Mode() + fileModePermissionBits = os.FileMode.Perm(mode) + ) + switch { + case mode&os.ModeDevice == 0: + return nil, ErrNotADevice + case mode&os.ModeCharDevice != 0: + fileModePermissionBits |= syscall.S_IFCHR + devType = 'c' + default: + fileModePermissionBits |= syscall.S_IFBLK + devType = 'b' + } + stat_t, ok := fileInfo.Sys().(*syscall.Stat_t) + if !ok { + return nil, fmt.Errorf("cannot determine the device number for device %s", path) + } + devNumber := int(stat_t.Rdev) + return &configs.Device{ + Type: devType, + Path: path, + Major: Major(devNumber), + Minor: Minor(devNumber), + Permissions: permissions, + FileMode: fileModePermissionBits, + Uid: stat_t.Uid, + Gid: stat_t.Gid, + }, nil +} + +func HostDevices() ([]*configs.Device, error) { + return getDevices("/dev") +} + +func getDevices(path string) ([]*configs.Device, error) { + files, err := ioutilReadDir(path) + if err != nil { + return nil, err + } + out := []*configs.Device{} + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + case "pts", "shm", "fd", "mqueue": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + + out = append(out, sub...) + continue + } + case f.Name() == "console": + continue + } + device, err := DeviceFromPath(filepath.Join(path, f.Name()), "rwm") + if err != nil { + if err == ErrNotADevice { + continue + } + return nil, err + } + out = append(out, device) + } + return out, nil +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_unsupported.go b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_unsupported.go new file mode 100644 index 0000000..1e84033 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/devices_unsupported.go @@ -0,0 +1,3 @@ +// +build windows + +package devices diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/devices/number.go b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/number.go new file mode 100644 index 0000000..885b6e5 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/devices/number.go @@ -0,0 +1,24 @@ +// +build linux freebsd + +package devices + +/* + +This code provides support for manipulating linux device numbers. It should be replaced by normal syscall functions once http://code.google.com/p/go/issues/detail?id=8106 is solved. + +You can read what they are here: + + - http://www.makelinux.net/ldd3/chp-3-sect-2 + - http://www.linux-tutorial.info/modules.php?name=MContent&pageid=94 + +Note! These are NOT the same as the MAJOR(dev_t device);, MINOR(dev_t device); and MKDEV(int major, int minor); functions as defined in as the representation of device numbers used by go is different than the one used internally to the kernel! - https://github.com/torvalds/linux/blob/master/include/linux/kdev_t.h#L9 + +*/ + +func Major(devNumber int) int64 { + return int64((devNumber >> 8) & 0xfff) +} + +func Minor(devNumber int) int64 { + return int64((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)) +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/error.go b/vendor/src/github.com/opencontainers/runc/libcontainer/error.go index aa59d2a..b50aaae 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/error.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/error.go @@ -16,9 +16,10 @@ const ( ContainerPaused ContainerNotStopped ContainerNotRunning + ContainerNotPaused // Process errors - ProcessNotExecuted + NoProcessOps // Common errors ConfigInvalid @@ -46,6 +47,10 @@ func (c ErrorCode) String() string { return "Container is not running" case ConsoleExists: return "Console exists for process" + case ContainerNotPaused: + return "Container is not paused" + case NoProcessOps: + return "No process operations" default: return "Unknown error" } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/error_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/error_test.go new file mode 100644 index 0000000..4bf4c9f --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/error_test.go @@ -0,0 +1,20 @@ +package libcontainer + +import "testing" + +func TestErrorCode(t *testing.T) { + codes := map[ErrorCode]string{ + IdInUse: "Id already in use", + InvalidIdFormat: "Invalid format", + ContainerPaused: "Container paused", + ConfigInvalid: "Invalid configuration", + SystemError: "System error", + ContainerNotExists: "Container does not exist", + } + + for code, expected := range codes { + if actual := code.String(); actual != expected { + t.Fatalf("expected string %q but received %q", expected, actual) + } + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go index c2d359e..9a282cf 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux.go @@ -5,7 +5,6 @@ package libcontainer import ( "encoding/json" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -19,6 +18,7 @@ import ( "github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs/validate" + "github.com/opencontainers/runc/libcontainer/utils" ) const ( @@ -202,8 +202,12 @@ func (l *LinuxFactory) Load(id string) (Container, error) { criuPath: l.CriuPath, cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths), root: containerRoot, + created: state.Created, + } + c.state = &createdState{c: c, s: Created} + if err := c.refreshState(); err != nil { + return nil, err } - c.state = &nullState{c: c} return c, nil } @@ -226,21 +230,29 @@ func (l *LinuxFactory) StartInitialization() (err error) { // clear the current process's environment to clean any libcontainer // specific env vars. os.Clearenv() + var i initer defer func() { // if we have an error during the initialization of the container's init then send it back to the // parent process in the form of an initError. if err != nil { - // ensure that any data sent from the parent is consumed so it doesn't - // receive ECONNRESET when the child writes to the pipe. - ioutil.ReadAll(pipe) - if err := json.NewEncoder(pipe).Encode(newSystemError(err)); err != nil { + if _, ok := i.(*linuxStandardInit); ok { + // Synchronisation only necessary for standard init. + if err := utils.WriteJSON(pipe, syncT{procError}); err != nil { + panic(err) + } + } + if err := utils.WriteJSON(pipe, newSystemError(err)); err != nil { + panic(err) + } + } else { + if err := utils.WriteJSON(pipe, syncT{procStart}); err != nil { panic(err) } } // ensure that this pipe is always closed pipe.Close() }() - i, err := newContainerInit(it, pipe) + i, err = newContainerInit(it, pipe) if err != nil { return err } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux_test.go new file mode 100644 index 0000000..b0c0f49 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/factory_linux_test.go @@ -0,0 +1,183 @@ +// +build linux + +package libcontainer + +import ( + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" + + "github.com/docker/docker/pkg/mount" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/utils" +) + +func newTestRoot() (string, error) { + dir, err := ioutil.TempDir("", "libcontainer") + if err != nil { + return "", err + } + return dir, nil +} + +func TestFactoryNew(t *testing.T) { + root, rerr := newTestRoot() + if rerr != nil { + t.Fatal(rerr) + } + defer os.RemoveAll(root) + factory, err := New(root, Cgroupfs) + if err != nil { + t.Fatal(err) + } + if factory == nil { + t.Fatal("factory should not be nil") + } + lfactory, ok := factory.(*LinuxFactory) + if !ok { + t.Fatal("expected linux factory returned on linux based systems") + } + if lfactory.Root != root { + t.Fatalf("expected factory root to be %q but received %q", root, lfactory.Root) + } + + if factory.Type() != "libcontainer" { + t.Fatalf("unexpected factory type: %q, expected %q", factory.Type(), "libcontainer") + } +} + +func TestFactoryNewTmpfs(t *testing.T) { + root, rerr := newTestRoot() + if rerr != nil { + t.Fatal(rerr) + } + defer os.RemoveAll(root) + factory, err := New(root, Cgroupfs, TmpfsRoot) + if err != nil { + t.Fatal(err) + } + if factory == nil { + t.Fatal("factory should not be nil") + } + lfactory, ok := factory.(*LinuxFactory) + if !ok { + t.Fatal("expected linux factory returned on linux based systems") + } + if lfactory.Root != root { + t.Fatalf("expected factory root to be %q but received %q", root, lfactory.Root) + } + + if factory.Type() != "libcontainer" { + t.Fatalf("unexpected factory type: %q, expected %q", factory.Type(), "libcontainer") + } + mounted, err := mount.Mounted(lfactory.Root) + if err != nil { + t.Fatal(err) + } + if !mounted { + t.Fatalf("Factory Root is not mounted") + } + mounts, err := mount.GetMounts() + if err != nil { + t.Fatal(err) + } + var found bool + for _, m := range mounts { + if m.Mountpoint == lfactory.Root { + if m.Fstype != "tmpfs" { + t.Fatalf("Fstype of root: %s, expected %s", m.Fstype, "tmpfs") + } + if m.Source != "tmpfs" { + t.Fatalf("Source of root: %s, expected %s", m.Source, "tmpfs") + } + found = true + } + } + if !found { + t.Fatalf("Factory Root is not listed in mounts list") + } + defer syscall.Unmount(root, syscall.MNT_DETACH) +} + +func TestFactoryLoadNotExists(t *testing.T) { + root, rerr := newTestRoot() + if rerr != nil { + t.Fatal(rerr) + } + defer os.RemoveAll(root) + factory, err := New(root, Cgroupfs) + if err != nil { + t.Fatal(err) + } + _, err = factory.Load("nocontainer") + if err == nil { + t.Fatal("expected nil error loading non-existing container") + } + lerr, ok := err.(Error) + if !ok { + t.Fatal("expected libcontainer error type") + } + if lerr.Code() != ContainerNotExists { + t.Fatalf("expected error code %s but received %s", ContainerNotExists, lerr.Code()) + } +} + +func TestFactoryLoadContainer(t *testing.T) { + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + // setup default container config and state for mocking + var ( + id = "1" + expectedConfig = &configs.Config{ + Rootfs: "/mycontainer/root", + } + expectedState = &State{ + BaseState: BaseState{ + InitProcessPid: 1024, + Config: *expectedConfig, + }, + } + ) + if err := os.Mkdir(filepath.Join(root, id), 0700); err != nil { + t.Fatal(err) + } + if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil { + t.Fatal(err) + } + factory, err := New(root, Cgroupfs) + if err != nil { + t.Fatal(err) + } + container, err := factory.Load(id) + if err != nil { + t.Fatal(err) + } + if container.ID() != id { + t.Fatalf("expected container id %q but received %q", id, container.ID()) + } + config := container.Config() + if config.Rootfs != expectedConfig.Rootfs { + t.Fatalf("expected rootfs %q but received %q", expectedConfig.Rootfs, config.Rootfs) + } + lcontainer, ok := container.(*linuxContainer) + if !ok { + t.Fatal("expected linux container on linux based systems") + } + if lcontainer.initProcess.pid() != expectedState.InitProcessPid { + t.Fatalf("expected init pid %d but received %d", expectedState.InitProcessPid, lcontainer.initProcess.pid()) + } +} + +func marshal(path string, v interface{}) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + return utils.WriteJSON(f, v) +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go b/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go index 6fbc2d7..924d637 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error.go @@ -9,6 +9,19 @@ import ( "github.com/opencontainers/runc/libcontainer/stacktrace" ) +type syncType uint8 + +const ( + procReady syncType = iota + procError + procStart + procRun +) + +type syncT struct { + Type syncType `json:"type"` +} + var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} Code: {{.ECode}} {{if .Message }} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error_test.go new file mode 100644 index 0000000..292d2a3 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/generic_error_test.go @@ -0,0 +1,14 @@ +package libcontainer + +import ( + "fmt" + "io/ioutil" + "testing" +) + +func TestErrorDetail(t *testing.T) { + err := newGenericError(fmt.Errorf("test error"), SystemError) + if derr := err.Detail(ioutil.Discard); derr != nil { + t.Fatal(derr) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go index ddb1186..918f103 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/init_linux.go @@ -5,6 +5,7 @@ package libcontainer import ( "encoding/json" "fmt" + "io" "io/ioutil" "net" "os" @@ -73,6 +74,7 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) { }, nil case initStandard: return &linuxStandardInit{ + pipe: pipe, parentPid: syscall.Getppid(), config: config, }, nil @@ -140,6 +142,27 @@ func finalizeNamespace(config *initConfig) error { return nil } +// syncParentReady sends to the given pipe a JSON payload which indicates that +// the init is ready to Exec the child process. It then waits for the parent to +// indicate that it is cleared to Exec. +func syncParentReady(pipe io.ReadWriter) error { + // Tell parent. + if err := utils.WriteJSON(pipe, syncT{procReady}); err != nil { + return err + } + // Wait for parent to give the all-clear. + var procSync syncT + if err := json.NewDecoder(pipe).Decode(&procSync); err != nil { + if err == io.EOF { + return fmt.Errorf("parent closed synchronisation channel") + } + if procSync.Type != procRun { + return fmt.Errorf("invalid synchronisation flag from parent") + } + } + return nil +} + // joinExistingNamespaces gets all the namespace paths specified for the container and // does a setns on the namespace fd so that the current process joins the namespace. func joinExistingNamespaces(namespaces []configs.Namespace) error { @@ -309,7 +332,7 @@ func killCgroupProcesses(m cgroups.Manager) error { if err := m.Freeze(configs.Frozen); err != nil { logrus.Warn(err) } - pids, err := m.GetPids() + pids, err := m.GetAllPids() if err != nil { m.Freeze(configs.Thawed) return err diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/checkpoint_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/checkpoint_test.go new file mode 100644 index 0000000..a71c172 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/checkpoint_test.go @@ -0,0 +1,204 @@ +package integration + +import ( + "bufio" + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "syscall" + "testing" + + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" +) + +func showFile(t *testing.T, fname string) error { + t.Logf("=== %s ===\n", fname) + + f, err := os.Open(fname) + if err != nil { + t.Log(err) + return err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + t.Log(scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return err + } + + t.Logf("=== END ===\n") + + return nil +} + +func TestCheckpoint(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + config.Mounts = append(config.Mounts, &configs.Mount{ + Destination: "/sys/fs/cgroup", + Device: "cgroup", + Flags: defaultMountFlags | syscall.MS_RDONLY, + }) + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + + var stdout bytes.Buffer + + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + Stdout: &stdout, + } + + err = container.Start(&pconfig) + stdinR.Close() + defer stdinW.Close() + if err != nil { + t.Fatal(err) + } + + pid, err := pconfig.Pid() + if err != nil { + t.Fatal(err) + } + + process, err := os.FindProcess(pid) + if err != nil { + t.Fatal(err) + } + + imagesDir, err := ioutil.TempDir("", "criu") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(imagesDir) + + checkpointOpts := &libcontainer.CriuOpts{ + ImagesDirectory: imagesDir, + WorkDirectory: imagesDir, + } + dumpLog := filepath.Join(checkpointOpts.WorkDirectory, "dump.log") + restoreLog := filepath.Join(checkpointOpts.WorkDirectory, "restore.log") + + if err := container.Checkpoint(checkpointOpts); err != nil { + showFile(t, dumpLog) + t.Fatal(err) + } + + state, err := container.Status() + if err != nil { + t.Fatal(err) + } + + if state != libcontainer.Running { + t.Fatal("Unexpected state checkpoint: ", state) + } + + stdinW.Close() + _, err = process.Wait() + if err != nil { + t.Fatal(err) + } + + // reload the container + container, err = factory.Load("test") + if err != nil { + t.Fatal(err) + } + + restoreStdinR, restoreStdinW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + + restoreProcessConfig := &libcontainer.Process{ + Cwd: "/", + Stdin: restoreStdinR, + Stdout: &stdout, + } + + err = container.Restore(restoreProcessConfig, checkpointOpts) + restoreStdinR.Close() + defer restoreStdinW.Close() + if err != nil { + showFile(t, restoreLog) + t.Fatal(err) + } + + state, err = container.Status() + if err != nil { + t.Fatal(err) + } + if state != libcontainer.Running { + t.Fatal("Unexpected restore state: ", state) + } + + pid, err = restoreProcessConfig.Pid() + if err != nil { + t.Fatal(err) + } + + process, err = os.FindProcess(pid) + if err != nil { + t.Fatal(err) + } + + _, err = restoreStdinW.WriteString("Hello!") + if err != nil { + t.Fatal(err) + } + + restoreStdinW.Close() + s, err := process.Wait() + if err != nil { + t.Fatal(err) + } + + if !s.Success() { + t.Fatal(s.String(), pid) + } + + output := string(stdout.Bytes()) + if !strings.Contains(output, "Hello!") { + t.Fatal("Did not restore the pipe correctly:", output) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/doc.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/doc.go new file mode 100644 index 0000000..87545bc --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/doc.go @@ -0,0 +1,2 @@ +// integration is used for integration testing of libcontainer +package integration diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/exec_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/exec_test.go new file mode 100644 index 0000000..ca8609c --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/exec_test.go @@ -0,0 +1,1363 @@ +package integration + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "testing" + + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/cgroups/systemd" + "github.com/opencontainers/runc/libcontainer/configs" +) + +func TestExecPS(t *testing.T) { + testExecPS(t, false) +} + +func TestUsernsExecPS(t *testing.T) { + if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { + t.Skip("userns is unsupported") + } + testExecPS(t, true) +} + +func testExecPS(t *testing.T, userns bool) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + if userns { + config.UidMappings = []configs.IDMap{{0, 0, 1000}} + config.GidMappings = []configs.IDMap{{0, 0, 1000}} + config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER}) + } + + buffers, exitCode, err := runContainer(config, "", "ps") + if err != nil { + t.Fatalf("%s: %s", buffers, err) + } + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + lines := strings.Split(buffers.Stdout.String(), "\n") + if len(lines) < 2 { + t.Fatalf("more than one process running for output %q", buffers.Stdout.String()) + } + expected := `1 root ps` + actual := strings.Trim(lines[1], "\n ") + if actual != expected { + t.Fatalf("expected output %q but received %q", expected, actual) + } +} + +func TestIPCPrivate(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + l, err := os.Readlink("/proc/1/ns/ipc") + ok(t, err) + + config := newTemplateConfig(rootfs) + buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") + ok(t, err) + + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + + if actual := strings.Trim(buffers.Stdout.String(), "\n"); actual == l { + t.Fatalf("ipc link should be private to the container but equals host %q %q", actual, l) + } +} + +func TestIPCHost(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + l, err := os.Readlink("/proc/1/ns/ipc") + ok(t, err) + + config := newTemplateConfig(rootfs) + config.Namespaces.Remove(configs.NEWIPC) + buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") + ok(t, err) + + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + + if actual := strings.Trim(buffers.Stdout.String(), "\n"); actual != l { + t.Fatalf("ipc link not equal to host link %q %q", actual, l) + } +} + +func TestIPCJoinPath(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + l, err := os.Readlink("/proc/1/ns/ipc") + ok(t, err) + + config := newTemplateConfig(rootfs) + config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc") + + buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") + ok(t, err) + + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + + if actual := strings.Trim(buffers.Stdout.String(), "\n"); actual != l { + t.Fatalf("ipc link not equal to host link %q %q", actual, l) + } +} + +func TestIPCBadPath(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipcc") + + _, _, err = runContainer(config, "", "true") + if err == nil { + t.Fatal("container succeeded with bad ipc path") + } +} + +func TestRlimit(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n") + ok(t, err) + if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" { + t.Fatalf("expected rlimit to be 1025, got %s", limit) + } +} + +func newTestRoot() (string, error) { + dir, err := ioutil.TempDir("", "libcontainer") + if err != nil { + return "", err + } + if err := os.MkdirAll(dir, 0700); err != nil { + return "", err + } + return dir, nil +} + +func TestEnter(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + + var stdout, stdout2 bytes.Buffer + + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "cat && readlink /proc/self/ns/pid"}, + Env: standardEnvironment, + Stdin: stdinR, + Stdout: &stdout, + } + err = container.Start(&pconfig) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + pid, err := pconfig.Pid() + ok(t, err) + + // Execute another process in the container + stdinR2, stdinW2, err := os.Pipe() + ok(t, err) + pconfig2 := libcontainer.Process{ + Cwd: "/", + Env: standardEnvironment, + } + pconfig2.Args = []string{"sh", "-c", "cat && readlink /proc/self/ns/pid"} + pconfig2.Stdin = stdinR2 + pconfig2.Stdout = &stdout2 + + err = container.Start(&pconfig2) + stdinR2.Close() + defer stdinW2.Close() + ok(t, err) + + pid2, err := pconfig2.Pid() + ok(t, err) + + processes, err := container.Processes() + ok(t, err) + + n := 0 + for i := range processes { + if processes[i] == pid || processes[i] == pid2 { + n++ + } + } + if n != 2 { + t.Fatal("unexpected number of processes", processes, pid, pid2) + } + + // Wait processes + stdinW2.Close() + waitProcess(&pconfig2, t) + + stdinW.Close() + waitProcess(&pconfig, t) + + // Check that both processes live in the same pidns + pidns := string(stdout.Bytes()) + ok(t, err) + + pidns2 := string(stdout2.Bytes()) + ok(t, err) + + if pidns != pidns2 { + t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2) + } +} + +func TestProcessEnv(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "env"}, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=integration", + "TERM=xterm", + "FOO=BAR", + }, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + outputEnv := string(stdout.Bytes()) + + // Check that the environment has the key/value pair we added + if !strings.Contains(outputEnv, "FOO=BAR") { + t.Fatal("Environment doesn't have the expected FOO=BAR key/value pair: ", outputEnv) + } + + // Make sure that HOME is set + if !strings.Contains(outputEnv, "HOME=/root") { + t.Fatal("Environment doesn't have HOME set: ", outputEnv) + } +} + +func TestProcessCaps(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + processCaps := append(config.Capabilities, "CAP_NET_ADMIN") + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "cat /proc/self/status"}, + Env: standardEnvironment, + Capabilities: processCaps, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + outputStatus := string(stdout.Bytes()) + + lines := strings.Split(outputStatus, "\n") + + effectiveCapsLine := "" + for _, l := range lines { + line := strings.TrimSpace(l) + if strings.Contains(line, "CapEff:") { + effectiveCapsLine = line + break + } + } + + if effectiveCapsLine == "" { + t.Fatal("Couldn't find effective caps: ", outputStatus) + } + + parts := strings.Split(effectiveCapsLine, ":") + effectiveCapsStr := strings.TrimSpace(parts[1]) + + effectiveCaps, err := strconv.ParseUint(effectiveCapsStr, 16, 64) + if err != nil { + t.Fatal("Could not parse effective caps", err) + } + + var netAdminMask uint64 + var netAdminBit uint + netAdminBit = 12 // from capability.h + netAdminMask = 1 << netAdminBit + if effectiveCaps&netAdminMask != netAdminMask { + t.Fatal("CAP_NET_ADMIN is not set as expected") + } +} + +func TestAdditionalGroups(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.AdditionalGroups = []string{"plugdev", "audio"} + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + ok(t, err) + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "id", "-Gn"}, + Env: standardEnvironment, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + outputGroups := string(stdout.Bytes()) + + // Check that the groups output has the groups that we specified + if !strings.Contains(outputGroups, "audio") { + t.Fatalf("Listed groups do not contain the audio group as expected: %v", outputGroups) + } + + if !strings.Contains(outputGroups, "plugdev") { + t.Fatalf("Listed groups do not contain the plugdev group as expected: %v", outputGroups) + } +} + +func TestFreeze(t *testing.T) { + testFreeze(t, false) +} + +func TestSystemdFreeze(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testFreeze(t, true) +} + +func testFreeze(t *testing.T, systemd bool) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + f := factory + if systemd { + f = systemdFactory + } + + container, err := f.Create("test", config) + ok(t, err) + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + + pconfig := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(pconfig) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + err = container.Pause() + ok(t, err) + state, err := container.Status() + ok(t, err) + err = container.Resume() + ok(t, err) + if state != libcontainer.Paused { + t.Fatal("Unexpected state: ", state) + } + + stdinW.Close() + waitProcess(pconfig, t) +} + +func TestCpuShares(t *testing.T) { + testCpuShares(t, false) +} + +func TestCpuSharesSystemd(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testCpuShares(t, true) +} + +func testCpuShares(t *testing.T, systemd bool) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if systemd { + config.Cgroups.Parent = "system.slice" + } + config.Cgroups.Resources.CpuShares = 1 + + _, _, err = runContainer(config, "", "ps") + if err == nil { + t.Fatalf("runContainer should failed with invalid CpuShares") + } +} + +func TestPids(t *testing.T) { + testPids(t, false) +} + +func TestPidsSystemd(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testPids(t, true) +} + +func testPids(t *testing.T, systemd bool) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if systemd { + config.Cgroups.Parent = "system.slice" + } + config.Cgroups.Resources.PidsLimit = -1 + + // Running multiple processes. + _, ret, err := runContainer(config, "", "/bin/sh", "-c", "/bin/true | /bin/true | /bin/true | /bin/true") + if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { + t.Skip("PIDs cgroup is unsupported") + } + ok(t, err) + + if ret != 0 { + t.Fatalf("expected fork() to succeed with no pids limit") + } + + // Enforce a permissive limit (shell + 6 * true + 3). + config.Cgroups.Resources.PidsLimit = 10 + _, ret, err = runContainer(config, "", "/bin/sh", "-c", "/bin/true | /bin/true | /bin/true | /bin/true | /bin/true | /bin/true") + if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { + t.Skip("PIDs cgroup is unsupported") + } + ok(t, err) + + if ret != 0 { + t.Fatalf("expected fork() to succeed with permissive pids limit") + } + + // Enforce a restrictive limit (shell + 6 * true + 3). + config.Cgroups.Resources.PidsLimit = 10 + out, ret, err := runContainer(config, "", "/bin/sh", "-c", "/bin/true | /bin/true | /bin/true | /bin/true | /bin/true | /bin/true | /bin/true | /bin/true | /bin/true | /bin/true") + if err != nil && strings.Contains(err.Error(), "no such directory for pids.max") { + t.Skip("PIDs cgroup is unsupported") + } + if err != nil && !strings.Contains(out.String(), "sh: can't fork") { + ok(t, err) + } + + if err == nil { + t.Fatalf("expected fork() to fail with restrictive pids limit") + } + + // Minimal restrictions are not really supported, due to quirks in using Go + // due to the fact that it spawns random processes. While we do our best with + // late setting cgroup values, it's just too unreliable with very small pids.max. + // As such, we don't test that case. YMMV. +} + +func TestRunWithKernelMemory(t *testing.T) { + testRunWithKernelMemory(t, false) +} + +func TestRunWithKernelMemorySystemd(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testRunWithKernelMemory(t, true) +} + +func testRunWithKernelMemory(t *testing.T, systemd bool) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if systemd { + config.Cgroups.Parent = "system.slice" + } + config.Cgroups.Resources.KernelMemory = 52428800 + + _, _, err = runContainer(config, "", "ps") + if err != nil { + t.Fatalf("runContainer failed with kernel memory limit: %v", err) + } +} + +func TestContainerState(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + l, err := os.Readlink("/proc/1/ns/ipc") + if err != nil { + t.Fatal(err) + } + + config := newTemplateConfig(rootfs) + config.Namespaces = configs.Namespaces([]configs.Namespace{ + {Type: configs.NEWNS}, + {Type: configs.NEWUTS}, + // host for IPC + //{Type: configs.NEWIPC}, + {Type: configs.NEWPID}, + {Type: configs.NEWNET}, + }) + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + p := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(p) + if err != nil { + t.Fatal(err) + } + stdinR.Close() + defer stdinW.Close() + + st, err := container.State() + if err != nil { + t.Fatal(err) + } + + l1, err := os.Readlink(st.NamespacePaths[configs.NEWIPC]) + if err != nil { + t.Fatal(err) + } + if l1 != l { + t.Fatal("Container using non-host ipc namespace") + } + stdinW.Close() + waitProcess(p, t) +} + +func TestPassExtraFiles(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + var stdout bytes.Buffer + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + process := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&process) + if err != nil { + t.Fatal(err) + } + + waitProcess(&process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out) + } + var buf = []byte{0} + _, err = pipeout1.Read(buf) + if err != nil { + t.Fatal(err) + } + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) + } + + _, err = pipeout2.Read(buf) + if err != nil { + t.Fatal(err) + } + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) + } +} + +func TestMountCmds(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + tmpDir, err := ioutil.TempDir("", "tmpdir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + config := newTemplateConfig(rootfs) + config.Mounts = append(config.Mounts, &configs.Mount{ + Source: tmpDir, + Destination: "/tmp", + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC, + PremountCmds: []configs.Command{ + {Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}}, + {Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}}, + }, + PostmountCmds: []configs.Command{ + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}}, + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}}, + }, + }) + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "env"}, + Env: standardEnvironment, + } + err = container.Start(&pconfig) + if err != nil { + t.Fatal(err) + } + + // Wait for process + waitProcess(&pconfig, t) + + entries, err := ioutil.ReadDir(tmpDir) + if err != nil { + t.Fatal(err) + } + expected := []string{"hello", "hello-backup", "world", "world-backup"} + for i, e := range entries { + if e.Name() != expected[i] { + t.Errorf("Got(%s), expect %s", e.Name(), expected[i]) + } + } +} + +func TestSysctl(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.Sysctl = map[string]string{ + "kernel.shmmni": "8192", + } + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "cat /proc/sys/kernel/shmmni"}, + Env: standardEnvironment, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + shmmniOutput := strings.TrimSpace(string(stdout.Bytes())) + if shmmniOutput != "8192" { + t.Fatalf("kernel.shmmni property expected to be 8192, but is %s", shmmniOutput) + } +} + +func TestMountCgroupRO(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + + config.Mounts = append(config.Mounts, &configs.Mount{ + Destination: "/sys/fs/cgroup", + Device: "cgroup", + Flags: defaultMountFlags | syscall.MS_RDONLY, + }) + + buffers, exitCode, err := runContainer(config, "", "mount") + if err != nil { + t.Fatalf("%s: %s", buffers, err) + } + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + mountInfo := buffers.Stdout.String() + lines := strings.Split(mountInfo, "\n") + for _, l := range lines { + if strings.HasPrefix(l, "tmpfs on /sys/fs/cgroup") { + if !strings.Contains(l, "ro") || + !strings.Contains(l, "nosuid") || + !strings.Contains(l, "nodev") || + !strings.Contains(l, "noexec") { + t.Fatalf("Mode expected to contain 'ro,nosuid,nodev,noexec': %s", l) + } + if !strings.Contains(l, "mode=755") { + t.Fatalf("Mode expected to contain 'mode=755': %s", l) + } + continue + } + if !strings.HasPrefix(l, "cgroup") { + continue + } + if !strings.Contains(l, "ro") || + !strings.Contains(l, "nosuid") || + !strings.Contains(l, "nodev") || + !strings.Contains(l, "noexec") { + t.Fatalf("Mode expected to contain 'ro,nosuid,nodev,noexec': %s", l) + } + } +} + +func TestMountCgroupRW(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + + config.Mounts = append(config.Mounts, &configs.Mount{ + Destination: "/sys/fs/cgroup", + Device: "cgroup", + Flags: defaultMountFlags, + }) + + buffers, exitCode, err := runContainer(config, "", "mount") + if err != nil { + t.Fatalf("%s: %s", buffers, err) + } + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + mountInfo := buffers.Stdout.String() + lines := strings.Split(mountInfo, "\n") + for _, l := range lines { + if strings.HasPrefix(l, "tmpfs on /sys/fs/cgroup") { + if !strings.Contains(l, "rw") || + !strings.Contains(l, "nosuid") || + !strings.Contains(l, "nodev") || + !strings.Contains(l, "noexec") { + t.Fatalf("Mode expected to contain 'rw,nosuid,nodev,noexec': %s", l) + } + if !strings.Contains(l, "mode=755") { + t.Fatalf("Mode expected to contain 'mode=755': %s", l) + } + continue + } + if !strings.HasPrefix(l, "cgroup") { + continue + } + if !strings.Contains(l, "rw") || + !strings.Contains(l, "nosuid") || + !strings.Contains(l, "nodev") || + !strings.Contains(l, "noexec") { + t.Fatalf("Mode expected to contain 'rw,nosuid,nodev,noexec': %s", l) + } + } +} + +func TestOomScoreAdj(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.OomScoreAdj = 200 + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + ok(t, err) + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "cat /proc/self/oom_score_adj"}, + Env: standardEnvironment, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + outputOomScoreAdj := strings.TrimSpace(string(stdout.Bytes())) + + // Check that the oom_score_adj matches the value that was set as part of config. + if outputOomScoreAdj != strconv.Itoa(config.OomScoreAdj) { + t.Fatalf("Expected oom_score_adj %d; got %q", config.OomScoreAdj, outputOomScoreAdj) + } +} + +func TestHook(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.Hooks = &configs.Hooks{ + Prestart: []configs.Hook{ + configs.NewFunctionHook(func(s configs.HookState) error { + f, err := os.Create(filepath.Join(s.Root, "test")) + if err != nil { + return err + } + return f.Close() + }), + }, + Poststop: []configs.Hook{ + configs.NewFunctionHook(func(s configs.HookState) error { + return os.RemoveAll(filepath.Join(s.Root, "test")) + }), + }, + } + container, err := factory.Create("test", config) + ok(t, err) + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "ls /test"}, + Env: standardEnvironment, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + outputLs := string(stdout.Bytes()) + + // Check that the ls output has the expected file touched by the prestart hook + if !strings.Contains(outputLs, "/test") { + container.Destroy() + t.Fatalf("ls output doesn't have the expected file: %s", outputLs) + } + + if err := container.Destroy(); err != nil { + t.Fatalf("container destory %s", err) + } + fi, err := os.Stat(filepath.Join(rootfs, "test")) + if err == nil || !os.IsNotExist(err) { + t.Fatalf("expected file to not exist, got %s", fi.Name()) + } +} + +func TestSTDIOPermissions(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + buffers, exitCode, err := runContainer(config, "", "sh", "-c", "echo hi > /dev/stderr") + ok(t, err) + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + + if actual := strings.Trim(buffers.Stderr.String(), "\n"); actual != "hi" { + t.Fatalf("stderr should equal be equal %q %q", actual, "hi") + } +} + +func unmountOp(path string) error { + if err := syscall.Unmount(path, syscall.MNT_DETACH); err != nil { + return err + } + return nil +} + +// Launch container with rootfsPropagation in rslave mode. Also +// bind mount a volume /mnt1host at /mnt1cont at the time of launch. Now do +// another mount on host (/mnt1host/mnt2host) and this new mount should +// propagate to container (/mnt1cont/mnt2host) +func TestRootfsPropagationSlaveMount(t *testing.T) { + var mountPropagated bool + var dir1cont string + var dir2cont string + + dir1cont = "/root/mnt1cont" + + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + + config.RootPropagation = syscall.MS_SLAVE | syscall.MS_REC + + // Bind mount a volume + dir1host, err := ioutil.TempDir("", "mnt1host") + ok(t, err) + defer os.RemoveAll(dir1host) + + // Make this dir a "shared" mount point. This will make sure a + // slave relationship can be established in container. + err = syscall.Mount(dir1host, dir1host, "bind", syscall.MS_BIND|syscall.MS_REC, "") + ok(t, err) + err = syscall.Mount("", dir1host, "", syscall.MS_SHARED|syscall.MS_REC, "") + ok(t, err) + defer unmountOp(dir1host) + + config.Mounts = append(config.Mounts, &configs.Mount{ + Source: dir1host, + Destination: dir1cont, + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC}) + + // TODO: systemd specific processing + f := factory + + container, err := f.Create("testSlaveMount", config) + ok(t, err) + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + + pconfig := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + + err = container.Start(pconfig) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + // Create mnt1host/mnt2host and bind mount itself on top of it. This + // should be visible in container. + dir2host, err := ioutil.TempDir(dir1host, "mnt2host") + ok(t, err) + defer os.RemoveAll(dir2host) + + err = syscall.Mount(dir2host, dir2host, "bind", syscall.MS_BIND, "") + defer unmountOp(dir2host) + ok(t, err) + + // Run "cat /proc/self/mountinfo" in container and look at mount points. + var stdout2 bytes.Buffer + + stdinR2, stdinW2, err := os.Pipe() + ok(t, err) + + pconfig2 := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat", "/proc/self/mountinfo"}, + Env: standardEnvironment, + Stdin: stdinR2, + Stdout: &stdout2, + } + + err = container.Start(pconfig2) + stdinR2.Close() + defer stdinW2.Close() + ok(t, err) + + // Wait for process + stdinW2.Close() + waitProcess(pconfig2, t) + stdinW.Close() + waitProcess(pconfig, t) + + mountPropagated = false + dir2cont = filepath.Join(dir1cont, filepath.Base(dir2host)) + + propagationInfo := string(stdout2.Bytes()) + lines := strings.Split(propagationInfo, "\n") + for _, l := range lines { + linefields := strings.Split(l, " ") + if len(linefields) < 5 { + continue + } + + if linefields[4] == dir2cont { + mountPropagated = true + break + } + } + + if mountPropagated != true { + t.Fatalf("Mount on host %s did not propagate in container at %s\n", dir2host, dir2cont) + } +} + +// Launch container with rootfsPropagation 0 so no propagation flags are +// applied. Also bind mount a volume /mnt1host at /mnt1cont at the time of +// launch. Now do a mount in container (/mnt1cont/mnt2cont) and this new +// mount should propagate to host (/mnt1host/mnt2cont) + +func TestRootfsPropagationSharedMount(t *testing.T) { + var dir1cont string + var dir2cont string + + dir1cont = "/root/mnt1cont" + + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + config.RootPropagation = syscall.MS_PRIVATE + + // Bind mount a volume + dir1host, err := ioutil.TempDir("", "mnt1host") + ok(t, err) + defer os.RemoveAll(dir1host) + + // Make this dir a "shared" mount point. This will make sure a + // shared relationship can be established in container. + err = syscall.Mount(dir1host, dir1host, "bind", syscall.MS_BIND|syscall.MS_REC, "") + ok(t, err) + err = syscall.Mount("", dir1host, "", syscall.MS_SHARED|syscall.MS_REC, "") + ok(t, err) + defer unmountOp(dir1host) + + config.Mounts = append(config.Mounts, &configs.Mount{ + Source: dir1host, + Destination: dir1cont, + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC}) + + // TODO: systemd specific processing + f := factory + + container, err := f.Create("testSharedMount", config) + ok(t, err) + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + + pconfig := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + + err = container.Start(pconfig) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + // Create mnt1host/mnt2cont. This will become visible inside container + // at mnt1cont/mnt2cont. Bind mount itself on top of it. This + // should be visible on host now. + dir2host, err := ioutil.TempDir(dir1host, "mnt2cont") + ok(t, err) + defer os.RemoveAll(dir2host) + + dir2cont = filepath.Join(dir1cont, filepath.Base(dir2host)) + + // Mount something in container and see if it is visible on host. + var stdout2 bytes.Buffer + + stdinR2, stdinW2, err := os.Pipe() + ok(t, err) + + // Provide CAP_SYS_ADMIN + processCaps := append(config.Capabilities, "CAP_SYS_ADMIN") + + pconfig2 := &libcontainer.Process{ + Cwd: "/", + Args: []string{"mount", "--bind", dir2cont, dir2cont}, + Env: standardEnvironment, + Stdin: stdinR2, + Stdout: &stdout2, + Capabilities: processCaps, + } + + err = container.Start(pconfig2) + stdinR2.Close() + defer stdinW2.Close() + ok(t, err) + + // Wait for process + stdinW2.Close() + waitProcess(pconfig2, t) + stdinW.Close() + waitProcess(pconfig, t) + + defer unmountOp(dir2host) + + // Check if mount is visible on host or not. + out, err := exec.Command("findmnt", "-n", "-f", "-oTARGET", dir2host).CombinedOutput() + outtrim := strings.TrimSpace(string(out)) + if err != nil { + t.Logf("findmnt error %q: %q", err, outtrim) + } + + if string(outtrim) != dir2host { + t.Fatalf("Mount in container on %s did not propagate to host on %s. finmnt output=%s", dir2cont, dir2host, outtrim) + } +} + +func TestPIDHost(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + l, err := os.Readlink("/proc/1/ns/pid") + ok(t, err) + + config := newTemplateConfig(rootfs) + config.Namespaces.Remove(configs.NEWPID) + buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/pid") + ok(t, err) + + if exitCode != 0 { + t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) + } + + if actual := strings.Trim(buffers.Stdout.String(), "\n"); actual != l { + t.Fatalf("ipc link not equal to host link %q %q", actual, l) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/execin_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/execin_test.go new file mode 100644 index 0000000..a80c958 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/execin_test.go @@ -0,0 +1,402 @@ +package integration + +import ( + "bytes" + "io" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/opencontainers/runc/libcontainer" +) + +func TestExecIn(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + buffers := newStdBuffers() + ps := &libcontainer.Process{ + Cwd: "/", + Args: []string{"ps"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(ps) + ok(t, err) + waitProcess(ps, t) + stdinW.Close() + waitProcess(process, t) + + out := buffers.Stdout.String() + if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecInRlimit(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + buffers := newStdBuffers() + ps := &libcontainer.Process{ + Cwd: "/", + Args: []string{"/bin/sh", "-c", "ulimit -n"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + err = container.Start(ps) + ok(t, err) + waitProcess(ps, t) + + stdinW.Close() + waitProcess(process, t) + + out := buffers.Stdout.String() + if limit := strings.TrimSpace(out); limit != "1025" { + t.Fatalf("expected rlimit to be 1025, got %s", limit) + } +} + +func TestExecInError(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer func() { + stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + }() + ok(t, err) + + for i := 0; i < 42; i++ { + var out bytes.Buffer + unexistent := &libcontainer.Process{ + Cwd: "/", + Args: []string{"unexistent"}, + Env: standardEnvironment, + Stdout: &out, + } + err = container.Start(unexistent) + if err == nil { + t.Fatal("Should be an error") + } + if !strings.Contains(err.Error(), "executable file not found") { + t.Fatalf("Should be error about not found executable, got %s", err) + } + if !bytes.Contains(out.Bytes(), []byte("executable file not found")) { + t.Fatalf("executable file not found error not delivered to stdio:\n%s", out.String()) + } + } +} + +func TestExecInTTY(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + var stdout bytes.Buffer + ps := &libcontainer.Process{ + Cwd: "/", + Args: []string{"ps"}, + Env: standardEnvironment, + } + console, err := ps.NewConsole(0) + copy := make(chan struct{}) + go func() { + io.Copy(&stdout, console) + close(copy) + }() + ok(t, err) + err = container.Start(ps) + ok(t, err) + select { + case <-time.After(5 * time.Second): + t.Fatal("Waiting for copy timed out") + case <-copy: + } + waitProcess(ps, t) + + stdinW.Close() + waitProcess(process, t) + + out := stdout.String() + if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecInEnvironment(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + buffers := newStdBuffers() + process2 := &libcontainer.Process{ + Cwd: "/", + Args: []string{"env"}, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "DEBUG=true", + "DEBUG=false", + "ENV=test", + }, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + err = container.Start(process2) + ok(t, err) + waitProcess(process2, t) + + stdinW.Close() + waitProcess(process, t) + + out := buffers.Stdout.String() + // check execin's process environment + if !strings.Contains(out, "DEBUG=false") || + !strings.Contains(out, "ENV=test") || + !strings.Contains(out, "HOME=/root") || + !strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") || + strings.Contains(out, "DEBUG=true") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecinPassExtraFiles(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + if err != nil { + t.Fatal(err) + } + + var stdout bytes.Buffer + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + inprocess := &libcontainer.Process{ + Cwd: "/", + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(inprocess) + if err != nil { + t.Fatal(err) + } + + waitProcess(inprocess, t) + stdinW.Close() + waitProcess(process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out) + } + var buf = []byte{0} + _, err = pipeout1.Read(buf) + if err != nil { + t.Fatal(err) + } + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) + } + + _, err = pipeout2.Read(buf) + if err != nil { + t.Fatal(err) + } + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) + } +} + +func TestExecInOomScoreAdj(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + config.OomScoreAdj = 200 + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + buffers := newStdBuffers() + ps := &libcontainer.Process{ + Cwd: "/", + Args: []string{"/bin/sh", "-c", "cat /proc/self/oom_score_adj"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + err = container.Start(ps) + ok(t, err) + waitProcess(ps, t) + + stdinW.Close() + waitProcess(process, t) + + out := buffers.Stdout.String() + if oomScoreAdj := strings.TrimSpace(out); oomScoreAdj != strconv.Itoa(config.OomScoreAdj) { + t.Fatalf("expected oomScoreAdj to be %d, got %s", config.OomScoreAdj, oomScoreAdj) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/init_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/init_test.go new file mode 100644 index 0000000..eaa6caf --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/init_test.go @@ -0,0 +1,60 @@ +package integration + +import ( + "os" + "runtime" + "testing" + + "github.com/Sirupsen/logrus" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/cgroups/systemd" + _ "github.com/opencontainers/runc/libcontainer/nsenter" +) + +// init runs the libcontainer initialization code because of the busybox style needs +// to work around the go runtime and the issues with forking +func init() { + if len(os.Args) < 2 || os.Args[1] != "init" { + return + } + runtime.GOMAXPROCS(1) + runtime.LockOSThread() + factory, err := libcontainer.New("") + if err != nil { + logrus.Fatalf("unable to initialize for container: %s", err) + } + if err := factory.StartInitialization(); err != nil { + logrus.Fatal(err) + } +} + +var ( + factory libcontainer.Factory + systemdFactory libcontainer.Factory +) + +func TestMain(m *testing.M) { + var ( + err error + ret int = 0 + ) + + logrus.SetOutput(os.Stderr) + logrus.SetLevel(logrus.InfoLevel) + + factory, err = libcontainer.New(".", libcontainer.Cgroupfs) + if err != nil { + logrus.Error(err) + os.Exit(1) + } + if systemd.UseSystemd() { + systemdFactory, err = libcontainer.New(".", libcontainer.SystemdCgroups) + if err != nil { + logrus.Error(err) + os.Exit(1) + } + } + + ret = m.Run() + os.Exit(ret) +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/seccomp_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/seccomp_test.go new file mode 100644 index 0000000..820773e --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/seccomp_test.go @@ -0,0 +1,219 @@ +// +build linux,cgo,seccomp + +package integration + +import ( + "strings" + "syscall" + "testing" + + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" + libseccomp "github.com/seccomp/libseccomp-golang" +) + +func TestSeccompDenyGetcwd(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.Seccomp = &configs.Seccomp{ + DefaultAction: configs.Allow, + Syscalls: []*configs.Syscall{ + { + Name: "getcwd", + Action: configs.Errno, + }, + }, + } + + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + buffers := newStdBuffers() + pwd := &libcontainer.Process{ + Cwd: "/", + Args: []string{"pwd"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(pwd) + if err != nil { + t.Fatal(err) + } + ps, err := pwd.Wait() + if err == nil { + t.Fatal("Expecting error (negative return code); instead exited cleanly!") + } + + var exitCode int + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + t.Fatalf("Unrecognized exit reason!") + } + + if exitCode == 0 { + t.Fatalf("Getcwd should fail with negative exit code, instead got %d!", exitCode) + } + + expected := "pwd: getcwd: Operation not permitted" + actual := strings.Trim(buffers.Stderr.String(), "\n") + if actual != expected { + t.Fatalf("Expected output %s but got %s\n", expected, actual) + } +} + +func TestSeccompPermitWriteConditional(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.Seccomp = &configs.Seccomp{ + DefaultAction: configs.Allow, + Syscalls: []*configs.Syscall{ + { + Name: "write", + Action: configs.Errno, + Args: []*configs.Arg{ + { + Index: 0, + Value: 1, + Op: configs.GreaterThan, + }, + }, + }, + }, + } + + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + buffers := newStdBuffers() + dmesg := &libcontainer.Process{ + Cwd: "/", + Args: []string{"busybox", "ls", "/"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(dmesg) + if err != nil { + t.Fatal(err) + } + if _, err := dmesg.Wait(); err != nil { + t.Fatalf("%s: %s", err, buffers.Stderr) + } +} + +func TestSeccompDenyWriteConditional(t *testing.T) { + if testing.Short() { + return + } + + // Only test if library version is v2.2.1 or higher + // Conditional filtering will always error in v2.2.0 and lower + major, minor, micro := libseccomp.GetLibraryVersion() + if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.Seccomp = &configs.Seccomp{ + DefaultAction: configs.Allow, + Syscalls: []*configs.Syscall{ + { + Name: "write", + Action: configs.Errno, + Args: []*configs.Arg{ + { + Index: 0, + Value: 1, + Op: configs.GreaterThan, + }, + }, + }, + }, + } + + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + buffers := newStdBuffers() + dmesg := &libcontainer.Process{ + Cwd: "/", + Args: []string{"busybox", "ls", "does_not_exist"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(dmesg) + if err != nil { + t.Fatal(err) + } + + ps, err := dmesg.Wait() + if err == nil { + t.Fatal("Expecting negative return, instead got 0!") + } + + var exitCode int + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + t.Fatalf("Unrecognized exit reason!") + } + + if exitCode == 0 { + t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode) + } + + // We're denying write to stderr, so we expect an empty buffer + expected := "" + actual := strings.Trim(buffers.Stderr.String(), "\n") + if actual != expected { + t.Fatalf("Expected output %s but got %s\n", expected, actual) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/template_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/template_test.go new file mode 100644 index 0000000..047a5f1 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/template_test.go @@ -0,0 +1,120 @@ +package integration + +import ( + "syscall" + + "github.com/opencontainers/runc/libcontainer/configs" +) + +var standardEnvironment = []string{ + "HOME=/root", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=integration", + "TERM=xterm", +} + +const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV + +// newTemplateConfig returns a base template for running a container +// +// it uses a network strategy of just setting a loopback interface +// and the default setup for devices +func newTemplateConfig(rootfs string) *configs.Config { + return &configs.Config{ + Rootfs: rootfs, + Capabilities: []string{ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE", + }, + Namespaces: configs.Namespaces([]configs.Namespace{ + {Type: configs.NEWNS}, + {Type: configs.NEWUTS}, + {Type: configs.NEWIPC}, + {Type: configs.NEWPID}, + {Type: configs.NEWNET}, + }), + Cgroups: &configs.Cgroup{ + Path: "integration/test", + Resources: &configs.Resources{ + MemorySwappiness: -1, + AllowAllDevices: false, + AllowedDevices: configs.DefaultAllowedDevices, + }, + }, + MaskPaths: []string{ + "/proc/kcore", + }, + ReadonlyPaths: []string{ + "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", + }, + Devices: configs.DefaultAutoCreatedDevices, + Hostname: "integration", + Mounts: []*configs.Mount{ + { + Source: "proc", + Destination: "/proc", + Device: "proc", + Flags: defaultMountFlags, + }, + { + Source: "tmpfs", + Destination: "/dev", + Device: "tmpfs", + Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, + Data: "mode=755", + }, + { + Source: "devpts", + Destination: "/dev/pts", + Device: "devpts", + Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, + Data: "newinstance,ptmxmode=0666,mode=0620,gid=5", + }, + { + Device: "tmpfs", + Source: "shm", + Destination: "/dev/shm", + Data: "mode=1777,size=65536k", + Flags: defaultMountFlags, + }, + { + Source: "mqueue", + Destination: "/dev/mqueue", + Device: "mqueue", + Flags: defaultMountFlags, + }, + { + Source: "sysfs", + Destination: "/sys", + Device: "sysfs", + Flags: defaultMountFlags | syscall.MS_RDONLY, + }, + }, + Networks: []*configs.Network{ + { + Type: "loopback", + Address: "127.0.0.1/0", + Gateway: "localhost", + }, + }, + Rlimits: []configs.Rlimit{ + { + Type: syscall.RLIMIT_NOFILE, + Hard: uint64(1025), + Soft: uint64(1025), + }, + }, + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/integration/utils_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/utils_test.go new file mode 100644 index 0000000..3dcd0bb --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/integration/utils_test.go @@ -0,0 +1,141 @@ +package integration + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "syscall" + "testing" + + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" +) + +func newStdBuffers() *stdBuffers { + return &stdBuffers{ + Stdin: bytes.NewBuffer(nil), + Stdout: bytes.NewBuffer(nil), + Stderr: bytes.NewBuffer(nil), + } +} + +type stdBuffers struct { + Stdin *bytes.Buffer + Stdout *bytes.Buffer + Stderr *bytes.Buffer +} + +func (b *stdBuffers) String() string { + s := []string{} + if b.Stderr != nil { + s = append(s, b.Stderr.String()) + } + if b.Stdout != nil { + s = append(s, b.Stdout.String()) + } + return strings.Join(s, "|") +} + +// ok fails the test if an err is not nil. +func ok(t testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error()) + } +} + +func waitProcess(p *libcontainer.Process, t *testing.T) { + _, file, line, _ := runtime.Caller(1) + status, err := p.Wait() + + if err != nil { + t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error()) + } + + if !status.Success() { + t.Fatalf("%s:%d: unexpected status: %s\n\n", filepath.Base(file), line, status.String()) + } +} + +// newRootfs creates a new tmp directory and copies the busybox root filesystem +func newRootfs() (string, error) { + dir, err := ioutil.TempDir("", "") + if err != nil { + return "", err + } + if err := os.MkdirAll(dir, 0700); err != nil { + return "", err + } + if err := copyBusybox(dir); err != nil { + return "", err + } + return dir, nil +} + +func remove(dir string) { + os.RemoveAll(dir) +} + +// copyBusybox copies the rootfs for a busybox container created for the test image +// into the new directory for the specific test +func copyBusybox(dest string) error { + out, err := exec.Command("sh", "-c", fmt.Sprintf("cp -R /busybox/* %s/", dest)).CombinedOutput() + if err != nil { + return fmt.Errorf("copy error %q: %q", err, out) + } + return nil +} + +func newContainer(config *configs.Config) (libcontainer.Container, error) { + f := factory + + if config.Cgroups != nil && config.Cgroups.Parent == "system.slice" { + f = systemdFactory + } + + return f.Create("testCT", config) +} + +// runContainer runs the container with the specific config and arguments +// +// buffers are returned containing the STDOUT and STDERR output for the run +// along with the exit code and any go error +func runContainer(config *configs.Config, console string, args ...string) (buffers *stdBuffers, exitCode int, err error) { + container, err := newContainer(config) + if err != nil { + return nil, -1, err + } + defer container.Destroy() + buffers = newStdBuffers() + process := &libcontainer.Process{ + Cwd: "/", + Args: args, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(process) + if err != nil { + return buffers, -1, err + } + ps, err := process.Wait() + if err != nil { + return buffers, -1, err + } + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + return buffers, -1, err + } + return +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/keys/keyctl.go b/vendor/src/github.com/opencontainers/runc/libcontainer/keys/keyctl.go new file mode 100644 index 0000000..c37ca21 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/keys/keyctl.go @@ -0,0 +1,67 @@ +// +build linux + +package keyctl + +import ( + "fmt" + "syscall" + "strings" + "strconv" + "unsafe" +) + +const KEYCTL_JOIN_SESSION_KEYRING = 1 +const KEYCTL_SETPERM = 5 +const KEYCTL_DESCRIBE = 6 + +type KeySerial uint32 + +func JoinSessionKeyring(name string) (KeySerial, error) { + var _name *byte = nil + var err error + + if len(name) > 0 { + _name, err = syscall.BytePtrFromString(name) + if err != nil { + return KeySerial(0), err + } + } + + sessKeyId, _, errn := syscall.Syscall(syscall.SYS_KEYCTL, KEYCTL_JOIN_SESSION_KEYRING, uintptr(unsafe.Pointer(_name)), 0) + if errn != 0 { + return 0, fmt.Errorf("could not create session key: %v", errn) + } + return KeySerial(sessKeyId), nil +} + +// modify permissions on a keyring by reading the current permissions, +// anding the bits with the given mask (clearing permissions) and setting +// additional permission bits +func ModKeyringPerm(ringId KeySerial, mask, setbits uint32) error { + dest := make([]byte, 1024) + destBytes := unsafe.Pointer(&dest[0]) + + if _, _, err := syscall.Syscall6(syscall.SYS_KEYCTL, uintptr(KEYCTL_DESCRIBE), uintptr(ringId), uintptr(destBytes), uintptr(len(dest)), 0, 0); err != 0 { + return err + } + + res := strings.Split(string(dest), ";") + if len(res) < 5 { + return fmt.Errorf("Destination buffer for key description is too small") + } + + // parse permissions + perm64, err := strconv.ParseUint(res[3], 16, 32) + if err != nil { + return err + } + + perm := (uint32(perm64) & mask) | setbits + + if _, _, err := syscall.Syscall(syscall.SYS_KEYCTL, uintptr(KEYCTL_SETPERM), uintptr(ringId), uintptr(perm)); err != 0 { + return err + } + + return nil +} + diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/label/label_selinux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/label/label_selinux_test.go new file mode 100644 index 0000000..c2a19f5 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/label/label_selinux_test.go @@ -0,0 +1,144 @@ +// +build selinux,linux + +package label + +import ( + "os" + "strings" + "testing" + + "github.com/opencontainers/runc/libcontainer/selinux" +) + +func TestInit(t *testing.T) { + if selinux.SelinuxEnabled() { + var testNull []string + plabel, mlabel, err := InitLabels(testNull) + if err != nil { + t.Log("InitLabels Failed") + t.Fatal(err) + } + testDisabled := []string{"disable"} + plabel, mlabel, err = InitLabels(testDisabled) + if err != nil { + t.Log("InitLabels Disabled Failed") + t.Fatal(err) + } + if plabel != "" { + t.Log("InitLabels Disabled Failed") + t.Fatal() + } + testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"} + plabel, mlabel, err = InitLabels(testUser) + if err != nil { + t.Log("InitLabels User Failed") + t.Fatal(err) + } + if plabel != "user_u:user_r:user_t:s0:c1,c15" || mlabel != "user_u:object_r:svirt_sandbox_file_t:s0:c1,c15" { + t.Log("InitLabels User Match Failed") + t.Log(plabel, mlabel) + t.Fatal(err) + } + + testBadData := []string{"user", "role:user_r", "type:user_t", "level:s0:c1,c15"} + plabel, mlabel, err = InitLabels(testBadData) + if err == nil { + t.Log("InitLabels Bad Failed") + t.Fatal(err) + } + } +} +func TestDuplicateLabel(t *testing.T) { + secopt := DupSecOpt("system_u:system_r:svirt_lxc_net_t:s0:c1,c2") + t.Log(secopt) + for _, opt := range secopt { + con := strings.SplitN(opt, ":", 3) + if len(con) != 3 || con[0] != "label" { + t.Errorf("Invalid DupSecOpt return value") + continue + } + if con[1] == "user" { + if con[2] != "system_u" { + t.Errorf("DupSecOpt Failed user incorrect") + } + continue + } + if con[1] == "role" { + if con[2] != "system_r" { + t.Errorf("DupSecOpt Failed role incorrect") + } + continue + } + if con[1] == "type" { + if con[2] != "svirt_lxc_net_t" { + t.Errorf("DupSecOpt Failed type incorrect") + } + continue + } + if con[1] == "level" { + if con[2] != "s0:c1,c2" { + t.Errorf("DupSecOpt Failed level incorrect") + } + continue + } + t.Errorf("DupSecOpt Failed invalid field %q", con[1]) + } + secopt = DisableSecOpt() + if secopt[0] != "label:disable" { + t.Errorf("DisableSecOpt Failed level incorrect") + } +} +func TestRelabel(t *testing.T) { + testdir := "/tmp/test" + if err := os.Mkdir(testdir, 0755); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(testdir) + label := "system_u:system_r:svirt_sandbox_file_t:s0:c1,c2" + if err := Relabel(testdir, "", true); err != nil { + t.Fatal("Relabel with no label failed: %v", err) + } + if err := Relabel(testdir, label, true); err != nil { + t.Fatal("Relabel shared failed: %v", err) + } + if err := Relabel(testdir, label, false); err != nil { + t.Fatal("Relabel unshared failed: %v", err) + } + if err := Relabel("/etc", label, false); err == nil { + t.Fatal("Relabel /etc succeeded") + } + if err := Relabel("/", label, false); err == nil { + t.Fatal("Relabel / succeeded") + } + if err := Relabel("/usr", label, false); err == nil { + t.Fatal("Relabel /usr succeeded") + } +} + +func TestValidate(t *testing.T) { + if err := Validate("zZ"); err != ErrIncompatibleLabel { + t.Fatalf("Expected incompatible error, got %v", err) + } + if err := Validate("Z"); err != nil { + t.Fatal(err) + } + if err := Validate("z"); err != nil { + t.Fatal(err) + } + if err := Validate(""); err != nil { + t.Fatal(err) + } +} + +func TestIsShared(t *testing.T) { + if shared := IsShared("Z"); shared { + t.Fatal("Expected label `Z` to not be shared, got %v", shared) + } + if shared := IsShared("z"); !shared { + t.Fatal("Expected label `z` to be shared, got %v", shared) + } + if shared := IsShared("Zz"); !shared { + t.Fatal("Expected label `Zz` to be shared, got %v", shared) + } + +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux.go index cf81e24..839a50c 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux.go @@ -12,31 +12,32 @@ import ( const oomCgroupName = "memory" -// notifyOnOOM returns channel on which you can expect event about OOM, -// if process died without OOM this channel will be closed. -// s is current *libcontainer.State for container. -func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { - dir := paths[oomCgroupName] - if dir == "" { - return nil, fmt.Errorf("There is no path for %q in state", oomCgroupName) - } - oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) +type PressureLevel uint + +const ( + LowPressure PressureLevel = iota + MediumPressure + CriticalPressure +) + +func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) { + evFile, err := os.Open(filepath.Join(cgDir, evName)) if err != nil { return nil, err } fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0) if syserr != 0 { - oomControl.Close() + evFile.Close() return nil, syserr } eventfd := os.NewFile(fd, "eventfd") - eventControlPath := filepath.Join(dir, "cgroup.event_control") - data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd()) + eventControlPath := filepath.Join(cgDir, "cgroup.event_control") + data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg) if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { eventfd.Close() - oomControl.Close() + evFile.Close() return nil, err } ch := make(chan struct{}) @@ -44,7 +45,7 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { defer func() { close(ch) eventfd.Close() - oomControl.Close() + evFile.Close() }() buf := make([]byte, 8) for { @@ -61,3 +62,28 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { }() return ch, nil } + +// notifyOnOOM returns channel on which you can expect event about OOM, +// if process died without OOM this channel will be closed. +func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { + dir := paths[oomCgroupName] + if dir == "" { + return nil, fmt.Errorf("path %q missing", oomCgroupName) + } + + return registerMemoryEvent(dir, "memory.oom_control", "") +} + +func notifyMemoryPressure(paths map[string]string, level PressureLevel) (<-chan struct{}, error) { + dir := paths[oomCgroupName] + if dir == "" { + return nil, fmt.Errorf("path %q missing", oomCgroupName) + } + + if level > CriticalPressure { + return nil, fmt.Errorf("invalid pressure level %d", level) + } + + levelStr := []string{"low", "medium", "critical"}[level] + return registerMemoryEvent(dir, "memory.pressure_level", levelStr) +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux_test.go new file mode 100644 index 0000000..9aa4f3b --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/notify_linux_test.go @@ -0,0 +1,128 @@ +// +build linux + +package libcontainer + +import ( + "encoding/binary" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" + "time" +) + +type notifyFunc func(paths map[string]string) (<-chan struct{}, error) + +func testMemoryNotification(t *testing.T, evName string, notify notifyFunc, targ string) { + memoryPath, err := ioutil.TempDir("", "testmemnotification-"+evName) + if err != nil { + t.Fatal(err) + } + evFile := filepath.Join(memoryPath, evName) + eventPath := filepath.Join(memoryPath, "cgroup.event_control") + if err := ioutil.WriteFile(evFile, []byte{}, 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(eventPath, []byte{}, 0700); err != nil { + t.Fatal(err) + } + paths := map[string]string{ + "memory": memoryPath, + } + ch, err := notify(paths) + if err != nil { + t.Fatal("expected no error, got:", err) + } + + data, err := ioutil.ReadFile(eventPath) + if err != nil { + t.Fatal("couldn't read event control file:", err) + } + + var eventFd, evFd int + var arg string + if targ != "" { + _, err = fmt.Sscanf(string(data), "%d %d %s", &eventFd, &evFd, &arg) + } else { + _, err = fmt.Sscanf(string(data), "%d %d", &eventFd, &evFd) + } + if err != nil || arg != targ { + t.Fatalf("invalid control data %q: %s", data, err) + } + + // re-open the eventfd + efd, err := syscall.Dup(eventFd) + if err != nil { + t.Fatal("unable to reopen eventfd:", err) + } + defer syscall.Close(efd) + + if err != nil { + t.Fatal("unable to dup event fd:", err) + } + + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, 1) + + if _, err := syscall.Write(efd, buf); err != nil { + t.Fatal("unable to write to eventfd:", err) + } + + select { + case <-ch: + case <-time.After(100 * time.Millisecond): + t.Fatal("no notification on channel after 100ms") + } + + // simulate what happens when a cgroup is destroyed by cleaning up and then + // writing to the eventfd. + if err := os.RemoveAll(memoryPath); err != nil { + t.Fatal(err) + } + if _, err := syscall.Write(efd, buf); err != nil { + t.Fatal("unable to write to eventfd:", err) + } + + // give things a moment to shut down + select { + case _, ok := <-ch: + if ok { + t.Fatal("expected no notification to be triggered") + } + case <-time.After(100 * time.Millisecond): + } + + if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(evFd), syscall.F_GETFD, 0); err != syscall.EBADF { + t.Error("expected event control to be closed") + } + + if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(eventFd), syscall.F_GETFD, 0); err != syscall.EBADF { + t.Error("expected event fd to be closed") + } +} + +func TestNotifyOnOOM(t *testing.T) { + f := func(paths map[string]string) (<-chan struct{}, error) { + return notifyOnOOM(paths) + } + + testMemoryNotification(t, "memory.oom_control", f, "") +} + +func TestNotifyMemoryPressure(t *testing.T) { + tests := map[PressureLevel]string{ + LowPressure: "low", + MediumPressure: "medium", + CriticalPressure: "critical", + } + + for level, arg := range tests { + f := func(paths map[string]string) (<-chan struct{}, error) { + return notifyMemoryPressure(paths, level) + } + + testMemoryNotification(t, "memory.pressure_level", f, arg) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_test.go new file mode 100644 index 0000000..976ae6b --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsenter_test.go @@ -0,0 +1,143 @@ +package nsenter + +import ( + "bytes" + "encoding/json" + "io" + "os" + "os/exec" + "strings" + "syscall" + "testing" + + "github.com/opencontainers/runc/libcontainer" + "github.com/vishvananda/netlink/nl" +) + +type pid struct { + Pid int `json:"Pid"` +} + +func TestNsenterAlivePid(t *testing.T) { + args := []string{"nsenter-exec"} + parent, child, err := newPipe() + if err != nil { + t.Fatalf("failed to create pipe %v", err) + } + + cmd := &exec.Cmd{ + Path: os.Args[0], + Args: args, + ExtraFiles: []*os.File{child}, + Env: []string{"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITPIPE=3"}, + } + + if err := cmd.Start(); err != nil { + t.Fatalf("nsenter failed to start %v", err) + } + r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0) + r.AddData(&libcontainer.Int32msg{ + Type: libcontainer.PidAttr, + Value: uint32(os.Getpid()), + }) + if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil { + t.Fatal(err) + } + decoder := json.NewDecoder(parent) + var pid *pid + + if err := decoder.Decode(&pid); err != nil { + t.Fatalf("%v", err) + } + + if err := cmd.Wait(); err != nil { + t.Fatalf("nsenter exits with a non-zero exit status") + } + p, err := os.FindProcess(pid.Pid) + if err != nil { + t.Fatalf("%v", err) + } + p.Wait() +} + +func TestNsenterInvalidPid(t *testing.T) { + args := []string{"nsenter-exec"} + parent, child, err := newPipe() + if err != nil { + t.Fatalf("failed to create pipe %v", err) + } + + cmd := &exec.Cmd{ + Path: os.Args[0], + Args: args, + ExtraFiles: []*os.File{child}, + Env: []string{"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITPIPE=3"}, + } + + if err := cmd.Start(); err != nil { + t.Fatal("nsenter exits with a zero exit status") + } + r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0) + r.AddData(&libcontainer.Int32msg{ + Type: libcontainer.PidAttr, + Value: 0, + }) + if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil { + t.Fatal(err) + } + + if err := cmd.Wait(); err == nil { + t.Fatal("nsenter exits with a zero exit status") + } +} + +func TestNsenterDeadPid(t *testing.T) { + deadCmd := exec.Command("true") + if err := deadCmd.Run(); err != nil { + t.Fatal(err) + } + args := []string{"nsenter-exec"} + parent, child, err := newPipe() + if err != nil { + t.Fatalf("failed to create pipe %v", err) + } + + cmd := &exec.Cmd{ + Path: os.Args[0], + Args: args, + ExtraFiles: []*os.File{child}, + Env: []string{"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITPIPE=3"}, + } + + if err := cmd.Start(); err != nil { + t.Fatal("nsenter exits with a zero exit status") + } + + r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0) + r.AddData(&libcontainer.Int32msg{ + Type: libcontainer.PidAttr, + Value: uint32(deadCmd.Process.Pid), + }) + if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil { + t.Fatal(err) + } + + if err := cmd.Wait(); err == nil { + t.Fatal("nsenter exits with a zero exit status") + } +} + +func init() { + if strings.HasPrefix(os.Args[0], "nsenter-") { + os.Exit(0) + } + return +} + +func newPipe() (parent *os.File, child *os.File, err error) { + fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c b/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c index 27e6e53..6634afc 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/nsenter/nsexec.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/process.go b/vendor/src/github.com/opencontainers/runc/libcontainer/process.go index 9661df8..8b4c558 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/process.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/process.go @@ -55,7 +55,7 @@ type Process struct { // Wait releases any resources associated with the Process func (p Process) Wait() (*os.ProcessState, error) { if p.ops == nil { - return nil, newGenericError(fmt.Errorf("invalid process"), ProcessNotExecuted) + return nil, newGenericError(fmt.Errorf("invalid process"), NoProcessOps) } return p.ops.wait() } @@ -65,7 +65,7 @@ func (p Process) Pid() (int, error) { // math.MinInt32 is returned here, because it's invalid value // for the kill() system call. if p.ops == nil { - return math.MinInt32, newGenericError(fmt.Errorf("invalid process"), ProcessNotExecuted) + return math.MinInt32, newGenericError(fmt.Errorf("invalid process"), NoProcessOps) } return p.ops.pid(), nil } @@ -73,7 +73,7 @@ func (p Process) Pid() (int, error) { // Signal sends a signal to the Process. func (p Process) Signal(sig os.Signal) error { if p.ops == nil { - return newGenericError(fmt.Errorf("invalid process"), ProcessNotExecuted) + return newGenericError(fmt.Errorf("invalid process"), NoProcessOps) } return p.ops.signal(sig) } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go index 114c71b..353c87e 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/process_linux.go @@ -5,6 +5,7 @@ package libcontainer import ( "encoding/json" "errors" + "fmt" "io" "os" "os/exec" @@ -15,6 +16,7 @@ import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/system" + "github.com/opencontainers/runc/libcontainer/utils" ) type parentProcess interface { @@ -83,9 +85,10 @@ func (p *setnsProcess) start() (err error) { return newSystemError(err) } } - if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil { + if err := utils.WriteJSON(p.parentPipe, p.config); err != nil { return newSystemError(err) } + if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { return newSystemError(err) } @@ -95,6 +98,7 @@ func (p *setnsProcess) start() (err error) { if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { return newSystemError(err) } + // Must be done after Shutdown so the child will exit and we can wait for it. if ierr != nil { p.wait() return newSystemError(ierr) @@ -198,7 +202,6 @@ func (p *initProcess) start() (err error) { return newSystemError(err) } p.setExternalDescriptors(fds) - // Do this before syncing with child so that no children // can escape the cgroup if err := p.manager.Apply(p.pid()); err != nil { @@ -229,13 +232,56 @@ func (p *initProcess) start() (err error) { if err := p.sendConfig(); err != nil { return newSystemError(err) } - // wait for the child process to fully complete and receive an error message - // if one was encoutered - var ierr *genericError - if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { + var ( + procSync syncT + sentRun bool + ierr *genericError + ) + +loop: + for { + if err := json.NewDecoder(p.parentPipe).Decode(&procSync); err != nil { + if err == io.EOF { + break loop + } + return newSystemError(err) + } + switch procSync.Type { + case procStart: + break loop + case procReady: + if err := p.manager.Set(p.config.Config); err != nil { + return newSystemError(err) + } + // Sync with child. + if err := utils.WriteJSON(p.parentPipe, syncT{procRun}); err != nil { + return newSystemError(err) + } + sentRun = true + case procError: + // wait for the child process to fully complete and receive an error message + // if one was encoutered + if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { + return newSystemError(err) + } + if ierr != nil { + break loop + } + // Programmer error. + panic("No error following JSON procError payload.") + default: + return newSystemError(fmt.Errorf("invalid JSON synchronisation payload from child")) + } + } + if !sentRun { + return newSystemError(fmt.Errorf("could not synchronise with container process")) + } + if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { return newSystemError(err) } + // Must be done after Shutdown so the child will exit and we can wait for it. if ierr != nil { + p.wait() return newSystemError(ierr) } return nil @@ -269,12 +315,10 @@ func (p *initProcess) startTime() (string, error) { } func (p *initProcess) sendConfig() error { - // send the state to the container's init process then shutdown writes for the parent - if err := json.NewEncoder(p.parentPipe).Encode(p.config); err != nil { - return err - } - // shutdown writes for the parent side of the pipe - return syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR) + // send the config to the container's init process, we don't use JSON Encode + // here because there might be a problem in JSON decoder in some cases, see: + // https://github.com/docker/docker/issues/14203#issuecomment-174177790 + return utils.WriteJSON(p.parentPipe, p.config) } func (p *initProcess) createNetworkInterfaces() error { diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go index 5a2fad8..5ea6ff0 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux.go @@ -18,6 +18,8 @@ import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/label" + "github.com/opencontainers/runc/libcontainer/system" + libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" ) const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV @@ -293,12 +295,30 @@ func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) { // checkMountDestination checks to ensure that the mount destination is not over the top of /proc. // dest is required to be an abs path and have any symlinks resolved before calling this function. func checkMountDestination(rootfs, dest string) error { - if filepath.Clean(rootfs) == filepath.Clean(dest) { + if libcontainerUtils.CleanPath(rootfs) == libcontainerUtils.CleanPath(dest) { return fmt.Errorf("mounting into / is prohibited") } invalidDestinations := []string{ "/proc", } + // White list, it should be sub directories of invalid destinations + validDestinations := []string{ + // These entries can be bind mounted by files emulated by fuse, + // so commands like top, free displays stats in container. + "/proc/cpuinfo", + "/proc/diskstats", + "/proc/meminfo", + "/proc/stats", + } + for _, valid := range validDestinations { + path, err := filepath.Rel(filepath.Join(rootfs, valid), dest) + if err != nil { + return err + } + if path == "." { + return nil + } + } for _, invalid := range invalidDestinations { path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest) if err != nil { @@ -365,11 +385,12 @@ func reOpenDevNull() error { // Create the device nodes in the container. func createDevices(config *configs.Config) error { + useBindMount := system.RunningInUserNS() || config.Namespaces.Contains(configs.NEWUSER) oldMask := syscall.Umask(0000) for _, node := range config.Devices { // containers running in a user namespace are not allowed to mknod // devices so we can just bind mount it from the host. - if err := createDeviceNode(config.Rootfs, node, config.Namespaces.Contains(configs.NEWUSER)); err != nil { + if err := createDeviceNode(config.Rootfs, node, useBindMount); err != nil { syscall.Umask(oldMask) return err } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux_test.go new file mode 100644 index 0000000..a3bb077 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/rootfs_linux_test.go @@ -0,0 +1,37 @@ +// +build linux + +package libcontainer + +import "testing" + +func TestCheckMountDestOnProc(t *testing.T) { + dest := "/rootfs/proc/" + err := checkMountDestination("/rootfs", dest) + if err == nil { + t.Fatal("destination inside proc should return an error") + } +} + +func TestCheckMountDestInSys(t *testing.T) { + dest := "/rootfs//sys/fs/cgroup" + err := checkMountDestination("/rootfs", dest) + if err != nil { + t.Fatal("destination inside /sys should not return an error") + } +} + +func TestCheckMountDestFalsePositive(t *testing.T) { + dest := "/rootfs/sysfiles/fs/cgroup" + err := checkMountDestination("/rootfs", dest) + if err != nil { + t.Fatal(err) + } +} + +func TestCheckMountRoot(t *testing.T) { + dest := "/rootfs" + err := checkMountDestination("/rootfs", dest) + if err == nil { + t.Fatal(err) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/fixtures/proc_self_status b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/fixtures/proc_self_status new file mode 100644 index 0000000..0e0084f --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/fixtures/proc_self_status @@ -0,0 +1,47 @@ +Name: cat +State: R (running) +Tgid: 19383 +Ngid: 0 +Pid: 19383 +PPid: 19275 +TracerPid: 0 +Uid: 1000 1000 1000 1000 +Gid: 1000 1000 1000 1000 +FDSize: 256 +Groups: 24 25 27 29 30 44 46 102 104 108 111 1000 1001 +NStgid: 19383 +NSpid: 19383 +NSpgid: 19383 +NSsid: 19275 +VmPeak: 5944 kB +VmSize: 5944 kB +VmLck: 0 kB +VmPin: 0 kB +VmHWM: 744 kB +VmRSS: 744 kB +VmData: 324 kB +VmStk: 136 kB +VmExe: 48 kB +VmLib: 1776 kB +VmPTE: 32 kB +VmPMD: 12 kB +VmSwap: 0 kB +Threads: 1 +SigQ: 0/30067 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: 0000000000000000 +SigIgn: 0000000000000080 +SigCgt: 0000000000000000 +CapInh: 0000000000000000 +CapPrm: 0000000000000000 +CapEff: 0000000000000000 +CapBnd: 0000003fffffffff +CapAmb: 0000000000000000 +Seccomp: 0 +Cpus_allowed: f +Cpus_allowed_list: 0-3 +Mems_allowed: 00000000,00000001 +Mems_allowed_list: 0 +voluntary_ctxt_switches: 0 +nonvoluntary_ctxt_switches: 1 diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux.go index aff1b63..623e227 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux.go @@ -3,8 +3,11 @@ package seccomp import ( + "bufio" "fmt" "log" + "os" + "strings" "syscall" "github.com/opencontainers/runc/libcontainer/configs" @@ -17,6 +20,9 @@ var ( actKill = libseccomp.ActKill actTrace = libseccomp.ActTrace.SetReturnCode(int16(syscall.EPERM)) actErrno = libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM)) + + // SeccompModeFilter refers to the syscall argument SECCOMP_MODE_FILTER. + SeccompModeFilter = uintptr(2) ) // Filters given syscalls in a container, preventing them from being used @@ -73,6 +79,24 @@ func InitSeccomp(config *configs.Seccomp) error { return nil } +// IsEnabled returns if the kernel has been configured to support seccomp. +func IsEnabled() bool { + // Try to read from /proc/self/status for kernels > 3.8 + s, err := parseStatusFile("/proc/self/status") + if err != nil { + // Check if Seccomp is supported, via CONFIG_SECCOMP. + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_SECCOMP, 0, 0); err != syscall.EINVAL { + // Make sure the kernel has CONFIG_SECCOMP_FILTER. + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECCOMP, SeccompModeFilter, 0); err != syscall.EINVAL { + return true + } + } + return false + } + _, ok := s["Seccomp"] + return ok +} + // Convert Libcontainer Action to Libseccomp ScmpAction func getAction(act configs.Action) (libseccomp.ScmpAction, error) { switch act { @@ -178,3 +202,30 @@ func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error { return nil } + +func parseStatusFile(path string) (map[string]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + s := bufio.NewScanner(f) + status := make(map[string]string) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := s.Text() + parts := strings.Split(text, ":") + + if len(parts) <= 1 { + continue + } + + status[parts[0]] = parts[1] + } + return status, nil +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux_test.go new file mode 100644 index 0000000..67a2ef6 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_linux_test.go @@ -0,0 +1,17 @@ +// +build linux,cgo,seccomp + +package seccomp + +import "testing" + +func TestParseStatusFile(t *testing.T) { + s, err := parseStatusFile("fixtures/proc_self_status") + if err != nil { + t.Fatal(err) + } + + if _, ok := s["Seccomp"]; !ok { + + t.Fatal("expected to find 'Seccomp' in the map but did not.") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_unsupported.go b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_unsupported.go index 87d3abb..888483e 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_unsupported.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/seccomp/seccomp_unsupported.go @@ -17,3 +17,8 @@ func InitSeccomp(config *configs.Seccomp) error { } return nil } + +// IsEnabled returns false, because it is not supported. +func IsEnabled() bool { + return false +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go new file mode 100644 index 0000000..88d612c --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux.go @@ -0,0 +1,477 @@ +// +build linux + +package selinux + +import ( + "bufio" + "crypto/rand" + "encoding/binary" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "syscall" + + "github.com/docker/docker/pkg/mount" + "github.com/opencontainers/runc/libcontainer/system" +) + +const ( + Enforcing = 1 + Permissive = 0 + Disabled = -1 + selinuxDir = "/etc/selinux/" + selinuxConfig = selinuxDir + "config" + selinuxTypeTag = "SELINUXTYPE" + selinuxTag = "SELINUX" + selinuxPath = "/sys/fs/selinux" + xattrNameSelinux = "security.selinux" + stRdOnly = 0x01 +) + +var ( + assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) + mcsList = make(map[string]bool) + selinuxfs = "unknown" + selinuxEnabled = false // Stores whether selinux is currently enabled + selinuxEnabledChecked = false // Stores whether selinux enablement has been checked or established yet +) + +type SELinuxContext map[string]string + +// SetDisabled disables selinux support for the package +func SetDisabled() { + selinuxEnabled, selinuxEnabledChecked = false, true +} + +// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs +// filesystem or an empty string if no mountpoint is found. Selinuxfs is +// a proc-like pseudo-filesystem that exposes the selinux policy API to +// processes. The existence of an selinuxfs mount is used to determine +// whether selinux is currently enabled or not. +func getSelinuxMountPoint() string { + if selinuxfs != "unknown" { + return selinuxfs + } + selinuxfs = "" + + mounts, err := mount.GetMounts() + if err != nil { + return selinuxfs + } + for _, mount := range mounts { + if mount.Fstype == "selinuxfs" { + selinuxfs = mount.Mountpoint + break + } + } + if selinuxfs != "" { + var buf syscall.Statfs_t + syscall.Statfs(selinuxfs, &buf) + if (buf.Flags & stRdOnly) == 1 { + selinuxfs = "" + } + } + return selinuxfs +} + +// SelinuxEnabled returns whether selinux is currently enabled. +func SelinuxEnabled() bool { + if selinuxEnabledChecked { + return selinuxEnabled + } + selinuxEnabledChecked = true + if fs := getSelinuxMountPoint(); fs != "" { + if con, _ := Getcon(); con != "kernel" { + selinuxEnabled = true + } + } + return selinuxEnabled +} + +func readConfig(target string) (value string) { + var ( + val, key string + bufin *bufio.Reader + ) + + in, err := os.Open(selinuxConfig) + if err != nil { + return "" + } + defer in.Close() + + bufin = bufio.NewReader(in) + + for done := false; !done; { + var line string + if line, err = bufin.ReadString('\n'); err != nil { + if err != io.EOF { + return "" + } + done = true + } + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) + if key == target { + return strings.Trim(val, "\"") + } + } + } + return "" +} + +func getSELinuxPolicyRoot() string { + return selinuxDir + readConfig(selinuxTypeTag) +} + +func readCon(name string) (string, error) { + var val string + + in, err := os.Open(name) + if err != nil { + return "", err + } + defer in.Close() + + _, err = fmt.Fscanf(in, "%s", &val) + return val, err +} + +// Setfilecon sets the SELinux label for this path or returns an error. +func Setfilecon(path string, scon string) error { + return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0) +} + +// Getfilecon returns the SELinux label for this path or returns an error. +func Getfilecon(path string) (string, error) { + con, err := system.Lgetxattr(path, xattrNameSelinux) + + // Trim the NUL byte at the end of the byte buffer, if present. + if con[len(con)-1] == '\x00' { + con = con[:len(con)-1] + } + return string(con), err +} + +func Setfscreatecon(scon string) error { + return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), scon) +} + +func Getfscreatecon() (string, error) { + return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid())) +} + +// Getcon returns the SELinux label of the current process thread, or an error. +func Getcon() (string, error) { + return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid())) +} + +// Getpidcon returns the SELinux label of the given pid, or an error. +func Getpidcon(pid int) (string, error) { + return readCon(fmt.Sprintf("/proc/%d/attr/current", pid)) +} + +func Getexeccon() (string, error) { + return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid())) +} + +func writeCon(name string, val string) error { + out, err := os.OpenFile(name, os.O_WRONLY, 0) + if err != nil { + return err + } + defer out.Close() + + if val != "" { + _, err = out.Write([]byte(val)) + } else { + _, err = out.Write(nil) + } + return err +} + +func Setexeccon(scon string) error { + return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon) +} + +func (c SELinuxContext) Get() string { + return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) +} + +func NewContext(scon string) SELinuxContext { + c := make(SELinuxContext) + + if len(scon) != 0 { + con := strings.SplitN(scon, ":", 4) + c["user"] = con[0] + c["role"] = con[1] + c["type"] = con[2] + c["level"] = con[3] + } + return c +} + +func ReserveLabel(scon string) { + if len(scon) != 0 { + con := strings.SplitN(scon, ":", 4) + mcsAdd(con[3]) + } +} + +func selinuxEnforcePath() string { + return fmt.Sprintf("%s/enforce", selinuxPath) +} + +func SelinuxGetEnforce() int { + var enforce int + + enforceS, err := readCon(selinuxEnforcePath()) + if err != nil { + return -1 + } + + enforce, err = strconv.Atoi(string(enforceS)) + if err != nil { + return -1 + } + return enforce +} + +func SelinuxSetEnforce(mode int) error { + return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode)) +} + +func SelinuxGetEnforceMode() int { + switch readConfig(selinuxTag) { + case "enforcing": + return Enforcing + case "permissive": + return Permissive + } + return Disabled +} + +func mcsAdd(mcs string) error { + if mcsList[mcs] { + return fmt.Errorf("MCS Label already exists") + } + mcsList[mcs] = true + return nil +} + +func mcsDelete(mcs string) { + mcsList[mcs] = false +} + +func IntToMcs(id int, catRange uint32) string { + var ( + SETSIZE = int(catRange) + TIER = SETSIZE + ORD = id + ) + + if id < 1 || id > 523776 { + return "" + } + + for ORD > TIER { + ORD = ORD - TIER + TIER -= 1 + } + TIER = SETSIZE - TIER + ORD = ORD + TIER + return fmt.Sprintf("s0:c%d,c%d", TIER, ORD) +} + +func uniqMcs(catRange uint32) string { + var ( + n uint32 + c1, c2 uint32 + mcs string + ) + + for { + binary.Read(rand.Reader, binary.LittleEndian, &n) + c1 = n % catRange + binary.Read(rand.Reader, binary.LittleEndian, &n) + c2 = n % catRange + if c1 == c2 { + continue + } else { + if c1 > c2 { + t := c1 + c1 = c2 + c2 = t + } + } + mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2) + if err := mcsAdd(mcs); err != nil { + continue + } + break + } + return mcs +} + +func FreeLxcContexts(scon string) { + if len(scon) != 0 { + con := strings.SplitN(scon, ":", 4) + mcsDelete(con[3]) + } +} + +func GetLxcContexts() (processLabel string, fileLabel string) { + var ( + val, key string + bufin *bufio.Reader + ) + + if !SelinuxEnabled() { + return "", "" + } + lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot()) + in, err := os.Open(lxcPath) + if err != nil { + return "", "" + } + defer in.Close() + + bufin = bufio.NewReader(in) + + for done := false; !done; { + var line string + if line, err = bufin.ReadString('\n'); err != nil { + if err == io.EOF { + done = true + } else { + goto exit + } + } + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) + if key == "process" { + processLabel = strings.Trim(val, "\"") + } + if key == "file" { + fileLabel = strings.Trim(val, "\"") + } + } + } + + if processLabel == "" || fileLabel == "" { + return "", "" + } + +exit: + // mcs := IntToMcs(os.Getpid(), 1024) + mcs := uniqMcs(1024) + scon := NewContext(processLabel) + scon["level"] = mcs + processLabel = scon.Get() + scon = NewContext(fileLabel) + scon["level"] = mcs + fileLabel = scon.Get() + return processLabel, fileLabel +} + +func SecurityCheckContext(val string) error { + return writeCon(fmt.Sprintf("%s.context", selinuxPath), val) +} + +func CopyLevel(src, dest string) (string, error) { + if src == "" { + return "", nil + } + if err := SecurityCheckContext(src); err != nil { + return "", err + } + if err := SecurityCheckContext(dest); err != nil { + return "", err + } + scon := NewContext(src) + tcon := NewContext(dest) + mcsDelete(tcon["level"]) + mcsAdd(scon["level"]) + tcon["level"] = scon["level"] + return tcon.Get(), nil +} + +// Prevent users from relabing system files +func badPrefix(fpath string) error { + var badprefixes = []string{"/usr"} + + for _, prefix := range badprefixes { + if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) { + return fmt.Errorf("Relabeling content in %s is not allowed.", prefix) + } + } + return nil +} + +// Change the fpath file object to the SELinux label scon. +// If the fpath is a directory and recurse is true Chcon will walk the +// directory tree setting the label +func Chcon(fpath string, scon string, recurse bool) error { + if scon == "" { + return nil + } + if err := badPrefix(fpath); err != nil { + return err + } + callback := func(p string, info os.FileInfo, err error) error { + return Setfilecon(p, scon) + } + + if recurse { + return filepath.Walk(fpath, callback) + } + + return Setfilecon(fpath, scon) +} + +// DupSecOpt takes an SELinux process label and returns security options that +// can will set the SELinux Type and Level for future container processes +func DupSecOpt(src string) []string { + if src == "" { + return nil + } + con := NewContext(src) + if con["user"] == "" || + con["role"] == "" || + con["type"] == "" || + con["level"] == "" { + return nil + } + return []string{"label:user:" + con["user"], + "label:role:" + con["role"], + "label:type:" + con["type"], + "label:level:" + con["level"]} +} + +// DisableSecOpt returns a security opt that can be used to disabling SELinux +// labeling support for future container processes +func DisableSecOpt() []string { + return []string{"label:disable"} +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux_test.go new file mode 100644 index 0000000..c2d561b --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/selinux/selinux_test.go @@ -0,0 +1,75 @@ +// +build linux,selinux + +package selinux_test + +import ( + "os" + "testing" + + "github.com/opencontainers/runc/libcontainer/selinux" +) + +func TestSetfilecon(t *testing.T) { + if selinux.SelinuxEnabled() { + tmp := "selinux_test" + out, _ := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE, 0) + out.Close() + err := selinux.Setfilecon(tmp, "system_u:object_r:bin_t:s0") + if err != nil { + t.Log("Setfilecon failed") + t.Fatal(err) + } + os.Remove(tmp) + } +} + +func TestSELinux(t *testing.T) { + var ( + err error + plabel, flabel string + ) + + if selinux.SelinuxEnabled() { + t.Log("Enabled") + plabel, flabel = selinux.GetLxcContexts() + t.Log(plabel) + t.Log(flabel) + selinux.FreeLxcContexts(plabel) + plabel, flabel = selinux.GetLxcContexts() + t.Log(plabel) + t.Log(flabel) + selinux.FreeLxcContexts(plabel) + t.Log("getenforce ", selinux.SelinuxGetEnforce()) + mode := selinux.SelinuxGetEnforceMode() + t.Log("getenforcemode ", mode) + + defer selinux.SelinuxSetEnforce(mode) + if err := selinux.SelinuxSetEnforce(selinux.Enforcing); err != nil { + t.Fatalf("enforcing selinux failed: %v", err) + } + if err := selinux.SelinuxSetEnforce(selinux.Permissive); err != nil { + t.Fatalf("setting selinux mode to permissive failed: %v", err) + } + selinux.SelinuxSetEnforce(mode) + + pid := os.Getpid() + t.Logf("PID:%d MCS:%s\n", pid, selinux.IntToMcs(pid, 1023)) + err = selinux.Setfscreatecon("unconfined_u:unconfined_r:unconfined_t:s0") + if err == nil { + t.Log(selinux.Getfscreatecon()) + } else { + t.Log("setfscreatecon failed", err) + t.Fatal(err) + } + err = selinux.Setfscreatecon("") + if err == nil { + t.Log(selinux.Getfscreatecon()) + } else { + t.Log("setfscreatecon failed", err) + t.Fatal(err) + } + t.Log(selinux.Getpidcon(1)) + } else { + t.Log("Disabled") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go index 2bde44f..cb9af7d 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/setns_init_linux.go @@ -6,6 +6,7 @@ import ( "os" "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/opencontainers/runc/libcontainer/keys" "github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/runc/libcontainer/seccomp" "github.com/opencontainers/runc/libcontainer/system" @@ -18,6 +19,10 @@ type linuxSetnsInit struct { } func (l *linuxSetnsInit) Init() error { + // do not inherit the parent's session keyring + if _, err := keyctl.JoinSessionKeyring("_ses"); err != nil { + return err + } if err := setupRlimits(l.config.Config); err != nil { return err } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/stacktrace/capture_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/stacktrace/capture_test.go new file mode 100644 index 0000000..8337930 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/stacktrace/capture_test.go @@ -0,0 +1,27 @@ +package stacktrace + +import "testing" + +func captureFunc() Stacktrace { + return Capture(0) +} + +func TestCaptureTestFunc(t *testing.T) { + stack := captureFunc() + + if len(stack.Frames) == 0 { + t.Fatal("expected stack frames to be returned") + } + + // the first frame is the caller + frame := stack.Frames[0] + if expected := "captureFunc"; frame.Function != expected { + t.Fatalf("expteced function %q but recevied %q", expected, frame.Function) + } + if expected := "github.com/opencontainers/runc/libcontainer/stacktrace"; frame.Package != expected { + t.Fatalf("expected package %q but received %q", expected, frame.Package) + } + if expected := "capture_test.go"; frame.File != expected { + t.Fatalf("expected file %q but received %q", expected, frame.File) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/stacktrace/frame_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/stacktrace/frame_test.go new file mode 100644 index 0000000..c6fc78e --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/stacktrace/frame_test.go @@ -0,0 +1,20 @@ +package stacktrace + +import "testing" + +func TestParsePackageName(t *testing.T) { + var ( + name = "github.com/opencontainers/runc/libcontainer/stacktrace.captureFunc" + expectedPackage = "github.com/opencontainers/runc/libcontainer/stacktrace" + expectedFunction = "captureFunc" + ) + + pack, funcName := parseFunctionName(name) + if pack != expectedPackage { + t.Fatalf("expected package %q but received %q", expectedPackage, pack) + } + + if funcName != expectedFunction { + t.Fatalf("expected function %q but received %q", expectedFunction, funcName) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go index ec10057..c17031d 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/standard_init_linux.go @@ -3,22 +3,37 @@ package libcontainer import ( + "io" "os" "syscall" "github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/keys" "github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/runc/libcontainer/seccomp" "github.com/opencontainers/runc/libcontainer/system" ) type linuxStandardInit struct { + pipe io.ReadWriter parentPid int config *initConfig } func (l *linuxStandardInit) Init() error { + // do not inherit the parent's session keyring + sessKeyId, err := keyctl.JoinSessionKeyring("") + if err != nil { + return err + } + // make session keyring searcheable + // without user ns we need 'UID' search permissions + // with user ns we need 'other' search permissions + if err := keyctl.ModKeyringPerm(sessKeyId, 0xffffffff, 0x080008); err != nil { + return err + } + // join any namespaces via a path to the namespace fd if provided if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil { return err @@ -50,7 +65,6 @@ func (l *linuxStandardInit) Init() error { if err := setOomScoreAdj(l.config.Config.OomScoreAdj); err != nil { return err } - label.Init() // InitializeMountNamespace() can be executed only for a new mount namespace if l.config.Config.Namespaces.Contains(configs.NEWNS) { @@ -75,7 +89,6 @@ func (l *linuxStandardInit) Init() error { return err } } - for _, path := range l.config.Config.ReadonlyPaths { if err := remountReadonly(path); err != nil { return err @@ -90,6 +103,12 @@ func (l *linuxStandardInit) Init() error { if err != nil { return err } + // Tell our parent that we're ready to Execv. This must be done before the + // Seccomp rules have been applied, because we need to be able to read and + // write to a socket. + if err := syncParentReady(l.pipe); err != nil { + return err + } if l.config.Config.Seccomp != nil { if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { return err diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux.go index 5e1bb73..9ffe15a 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux.go @@ -49,6 +49,7 @@ func destroy(c *linuxContainer) error { if herr := runPoststopHooks(c); err == nil { err = herr } + c.state = &stoppedState{c: c} return err } @@ -119,7 +120,7 @@ func (r *runningState) transition(s containerState) error { case *pausedState: r.c.state = s return nil - case *runningState, *nullState: + case *runningState: return nil } return newStateTransitionError(r, s) @@ -148,7 +149,7 @@ func (p *pausedState) status() Status { func (p *pausedState) transition(s containerState) error { switch s.(type) { - case *runningState: + case *runningState, *stoppedState: p.c.state = s return nil case *pausedState: @@ -158,6 +159,16 @@ func (p *pausedState) transition(s containerState) error { } func (p *pausedState) destroy() error { + isRunning, err := p.c.isRunning() + if err != nil { + return err + } + if !isRunning { + if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil { + return err + } + return destroy(p.c) + } return newGenericError(fmt.Errorf("container is paused"), ContainerPaused) } @@ -191,27 +202,25 @@ func (r *restoredState) destroy() error { return destroy(r.c) } -// nullState is used whenever a container is restored, loaded, or setting additional +// createdState is used whenever a container is restored, loaded, or setting additional // processes inside and it should not be destroyed when it is exiting. -type nullState struct { +type createdState struct { c *linuxContainer s Status } -func (n *nullState) status() Status { +func (n *createdState) status() Status { return n.s } -func (n *nullState) transition(s containerState) error { - switch s.(type) { - case *restoredState: - n.c.state = s - default: - // do nothing for null states - } +func (n *createdState) transition(s containerState) error { + n.c.state = s return nil } -func (n *nullState) destroy() error { - return nil +func (n *createdState) destroy() error { + if err := n.c.refreshState(); err != nil { + return err + } + return n.c.state.destroy() } diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux_test.go new file mode 100644 index 0000000..417d9c2 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/state_linux_test.go @@ -0,0 +1,79 @@ +// +build linux + +package libcontainer + +import "testing" + +func TestStateStatus(t *testing.T) { + states := map[containerState]Status{ + &stoppedState{}: Destroyed, + &runningState{}: Running, + &restoredState{}: Running, + &pausedState{}: Paused, + } + for s, status := range states { + if s.status() != status { + t.Fatalf("state returned %s but expected %s", s.status(), status) + } + } +} + +func isStateTransitionError(err error) bool { + _, ok := err.(*stateTransitionError) + return ok +} + +func TestStoppedStateTransition(t *testing.T) { + s := &stoppedState{c: &linuxContainer{}} + valid := []containerState{ + &stoppedState{}, + &runningState{}, + &restoredState{}, + } + for _, v := range valid { + if err := s.transition(v); err != nil { + t.Fatal(err) + } + } + err := s.transition(&pausedState{}) + if err == nil { + t.Fatal("transition to paused state should fail") + } + if !isStateTransitionError(err) { + t.Fatal("expected stateTransitionError") + } +} + +func TestPausedStateTransition(t *testing.T) { + s := &pausedState{c: &linuxContainer{}} + valid := []containerState{ + &pausedState{}, + &runningState{}, + &stoppedState{}, + } + for _, v := range valid { + if err := s.transition(v); err != nil { + t.Fatal(err) + } + } +} + +func TestRestoredStateTransition(t *testing.T) { + s := &restoredState{c: &linuxContainer{}} + valid := []containerState{ + &stoppedState{}, + &runningState{}, + } + for _, v := range valid { + if err := s.transition(v); err != nil { + t.Fatal(err) + } + } + err := s.transition(&createdState{}) + if err == nil { + t.Fatal("transition to created state should fail") + } + if !isStateTransitionError(err) { + t.Fatal("expected stateTransitionError") + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/system/linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/system/linux.go index 2cc3ef8..6c835e6 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/system/linux.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/system/linux.go @@ -3,6 +3,9 @@ package system import ( + "bufio" + "fmt" + "os" "os/exec" "syscall" "unsafe" @@ -75,3 +78,37 @@ func Setctty() error { } return nil } + +/* + * Detect whether we are currently running in a user namespace. + * Copied from github.com/lxc/lxd/shared/util.go + */ +func RunningInUserNS() bool { + file, err := os.Open("/proc/self/uid_map") + if err != nil { + /* + * This kernel-provided file only exists if user namespaces are + * supported + */ + return false + } + defer file.Close() + + buf := bufio.NewReader(file) + l, _, err := buf.ReadLine() + if err != nil { + return false + } + + line := string(l) + var a, b, c int64 + fmt.Sscanf(line, "%d %d %d", &a, &b, &c) + /* + * We assume we are in the initial user namespace if we have a full + * range - 4294967295 uids starting at uid 0. + */ + if a == 0 && b == 0 && c == 4294967295 { + return false + } + return true +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/user/user_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/user/user_test.go new file mode 100644 index 0000000..53b2289 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/user/user_test.go @@ -0,0 +1,472 @@ +package user + +import ( + "io" + "reflect" + "sort" + "strconv" + "strings" + "testing" +) + +func TestUserParseLine(t *testing.T) { + var ( + a, b string + c []string + d int + ) + + parseLine("", &a, &b) + if a != "" || b != "" { + t.Fatalf("a and b should be empty ('%v', '%v')", a, b) + } + + parseLine("a", &a, &b) + if a != "a" || b != "" { + t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b) + } + + parseLine("bad boys:corny cows", &a, &b) + if a != "bad boys" || b != "corny cows" { + t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b) + } + + parseLine("", &c) + if len(c) != 0 { + t.Fatalf("c should be empty (%#v)", c) + } + + parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c) + if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" { + t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("::::::::::", &a, &b, &c) + if a != "" || b != "" || len(c) != 0 { + t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("not a number", &d) + if d != 0 { + t.Fatalf("d should be 0 (%v)", d) + } + + parseLine("b:12:c", &a, &d, &b) + if a != "b" || b != "c" || d != 12 { + t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d) + } +} + +func TestUserParsePasswd(t *testing.T) { + users, err := ParsePasswdFilter(strings.NewReader(` +root:x:0:0:root:/root:/bin/bash +adm:x:3:4:adm:/var/adm:/bin/false +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(users) != 3 { + t.Fatalf("Expected 3 users, got %v", len(users)) + } + if users[0].Uid != 0 || users[0].Name != "root" { + t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name) + } + if users[1].Uid != 3 || users[1].Name != "adm" { + t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name) + } +} + +func TestUserParseGroup(t *testing.T) { + groups, err := ParseGroupFilter(strings.NewReader(` +root:x:0:root +adm:x:4:root,adm,daemon +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(groups) != 3 { + t.Fatalf("Expected 3 groups, got %v", len(groups)) + } + if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 { + t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List)) + } + if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 { + t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List)) + } +} + +func TestValidGetExecUser(t *testing.T) { + const passwdContent = ` +root:x:0:0:root user:/root:/bin/bash +adm:x:42:43:adm:/var/adm:/bin/false +this is just some garbage data +` + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +this is just some garbage data +` + defaultExecUser := ExecUser{ + Uid: 8888, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + } + + tests := []struct { + ref string + expected ExecUser + }{ + { + ref: "root", + expected: ExecUser{ + Uid: 0, + Gid: 0, + Sgids: []int{0, 1234}, + Home: "/root", + }, + }, + { + ref: "adm", + expected: ExecUser{ + Uid: 42, + Gid: 43, + Sgids: []int{1234}, + Home: "/var/adm", + }, + }, + { + ref: "root:adm", + expected: ExecUser{ + Uid: 0, + Gid: 43, + Sgids: defaultExecUser.Sgids, + Home: "/root", + }, + }, + { + ref: "adm:1234", + expected: ExecUser{ + Uid: 42, + Gid: 1234, + Sgids: defaultExecUser.Sgids, + Home: "/var/adm", + }, + }, + { + ref: "42:1234", + expected: ExecUser{ + Uid: 42, + Gid: 1234, + Sgids: defaultExecUser.Sgids, + Home: "/var/adm", + }, + }, + { + ref: "1337:1234", + expected: ExecUser{ + Uid: 1337, + Gid: 1234, + Sgids: defaultExecUser.Sgids, + Home: defaultExecUser.Home, + }, + }, + { + ref: "1337", + expected: ExecUser{ + Uid: 1337, + Gid: defaultExecUser.Gid, + Sgids: defaultExecUser.Sgids, + Home: defaultExecUser.Home, + }, + }, + { + ref: "", + expected: ExecUser{ + Uid: defaultExecUser.Uid, + Gid: defaultExecUser.Gid, + Sgids: defaultExecUser.Sgids, + Home: defaultExecUser.Home, + }, + }, + } + + for _, test := range tests { + passwd := strings.NewReader(passwdContent) + group := strings.NewReader(groupContent) + + execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) + if err != nil { + t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) + t.Fail() + continue + } + + if !reflect.DeepEqual(test.expected, *execUser) { + t.Logf("got: %#v", execUser) + t.Logf("expected: %#v", test.expected) + t.Fail() + continue + } + } +} + +func TestInvalidGetExecUser(t *testing.T) { + const passwdContent = ` +root:x:0:0:root user:/root:/bin/bash +adm:x:42:43:adm:/var/adm:/bin/false +this is just some garbage data +` + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +this is just some garbage data +` + + tests := []string{ + // No such user/group. + "notuser", + "notuser:notgroup", + "root:notgroup", + "notuser:adm", + "8888:notgroup", + "notuser:8888", + + // Invalid user/group values. + "-1:0", + "0:-3", + "-5:-2", + } + + for _, test := range tests { + passwd := strings.NewReader(passwdContent) + group := strings.NewReader(groupContent) + + execUser, err := GetExecUser(test, nil, passwd, group) + if err == nil { + t.Logf("got unexpected success when parsing '%s': %#v", test, execUser) + t.Fail() + continue + } + } +} + +func TestGetExecUserNilSources(t *testing.T) { + const passwdContent = ` +root:x:0:0:root user:/root:/bin/bash +adm:x:42:43:adm:/var/adm:/bin/false +this is just some garbage data +` + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +this is just some garbage data +` + + defaultExecUser := ExecUser{ + Uid: 8888, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + } + + tests := []struct { + ref string + passwd, group bool + expected ExecUser + }{ + { + ref: "", + passwd: false, + group: false, + expected: ExecUser{ + Uid: 8888, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + }, + }, + { + ref: "root", + passwd: true, + group: false, + expected: ExecUser{ + Uid: 0, + Gid: 0, + Sgids: []int{8888}, + Home: "/root", + }, + }, + { + ref: "0", + passwd: false, + group: false, + expected: ExecUser{ + Uid: 0, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + }, + }, + { + ref: "0:0", + passwd: false, + group: false, + expected: ExecUser{ + Uid: 0, + Gid: 0, + Sgids: []int{8888}, + Home: "/8888", + }, + }, + } + + for _, test := range tests { + var passwd, group io.Reader + + if test.passwd { + passwd = strings.NewReader(passwdContent) + } + + if test.group { + group = strings.NewReader(groupContent) + } + + execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) + if err != nil { + t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) + t.Fail() + continue + } + + if !reflect.DeepEqual(test.expected, *execUser) { + t.Logf("got: %#v", execUser) + t.Logf("expected: %#v", test.expected) + t.Fail() + continue + } + } +} + +func TestGetAdditionalGroups(t *testing.T) { + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +adm:x:4343:root,adm-duplicate +this is just some garbage data +` + tests := []struct { + groups []string + expected []int + hasError bool + }{ + { + // empty group + groups: []string{}, + expected: []int{}, + }, + { + // single group + groups: []string{"adm"}, + expected: []int{43}, + }, + { + // multiple groups + groups: []string{"adm", "grp"}, + expected: []int{43, 1234}, + }, + { + // invalid group + groups: []string{"adm", "grp", "not-exist"}, + expected: nil, + hasError: true, + }, + { + // group with numeric id + groups: []string{"43"}, + expected: []int{43}, + }, + { + // group with unknown numeric id + groups: []string{"adm", "10001"}, + expected: []int{43, 10001}, + }, + { + // groups specified twice with numeric and name + groups: []string{"adm", "43"}, + expected: []int{43}, + }, + { + // groups with too small id + groups: []string{"-1"}, + expected: nil, + hasError: true, + }, + { + // groups with too large id + groups: []string{strconv.Itoa(1 << 31)}, + expected: nil, + hasError: true, + }, + } + + for _, test := range tests { + group := strings.NewReader(groupContent) + + gids, err := GetAdditionalGroups(test.groups, group) + if test.hasError && err == nil { + t.Errorf("Parse(%#v) expects error but has none", test) + continue + } + if !test.hasError && err != nil { + t.Errorf("Parse(%#v) has error %v", test, err) + continue + } + sort.Sort(sort.IntSlice(gids)) + if !reflect.DeepEqual(gids, test.expected) { + t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups) + } + } +} + +func TestGetAdditionalGroupsNumeric(t *testing.T) { + tests := []struct { + groups []string + expected []int + hasError bool + }{ + { + // numeric groups only + groups: []string{"1234", "5678"}, + expected: []int{1234, 5678}, + }, + { + // numeric and alphabetic + groups: []string{"1234", "fake"}, + expected: nil, + hasError: true, + }, + } + + for _, test := range tests { + gids, err := GetAdditionalGroups(test.groups, nil) + if test.hasError && err == nil { + t.Errorf("Parse(%#v) expects error but has none", test) + continue + } + if !test.hasError && err != nil { + t.Errorf("Parse(%#v) has error %v", test, err) + continue + } + sort.Sort(sort.IntSlice(gids)) + if !reflect.DeepEqual(gids, test.expected) { + t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups) + } + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils.go b/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils.go index 86cf1d6..1f5528f 100644 --- a/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils.go +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils.go @@ -3,7 +3,9 @@ package utils import ( "crypto/rand" "encoding/hex" + "encoding/json" "io" + "os" "path/filepath" "syscall" ) @@ -36,10 +38,44 @@ func ResolveRootfs(uncleanRootfs string) (string, error) { } // ExitStatus returns the correct exit status for a process based on if it -// was signaled or exited cleanly. +// was signaled or exited cleanly func ExitStatus(status syscall.WaitStatus) int { if status.Signaled() { return exitSignalOffset + int(status.Signal()) } return status.ExitStatus() } + +// WriteJSON writes the provided struct v to w using standard json marshaling +func WriteJSON(w io.Writer, v interface{}) error { + data, err := json.Marshal(v) + if err != nil { + return err + } + _, err = w.Write(data) + return err +} + +// CleanPath makes a path safe for use with filepath.Join. This is done by not +// only cleaning the path, but also (if the path is relative) adding a leading +// '/' and cleaning it (then removing the leading '/'). This ensures that a +// path resulting from prepending another path will always resolve to lexically +// be a subdirectory of the prefixed path. This is all done lexically, so paths +// that include symlinks won't be safe as a result of using CleanPath. +func CleanPath(path string) string { + // Ensure that all paths are cleaned (especially problematic ones like + // "/../../../../../" which can cause lots of issues). + path = filepath.Clean(path) + + // If the path isn't absolute, we need to do more processing to fix paths + // such as "../../../..//some/path". We also shouldn't convert absolute + // paths to relative ones. + if !filepath.IsAbs(path) { + path = filepath.Clean(string(os.PathSeparator) + path) + // This can't fail, as (by definition) all paths are relative to root. + path, _ = filepath.Rel(string(os.PathSeparator), path) + } + + // Clean the path again for good measure. + return filepath.Clean(path) +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils_test.go new file mode 100644 index 0000000..813180a --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/utils/utils_test.go @@ -0,0 +1,25 @@ +package utils + +import "testing" + +func TestGenerateName(t *testing.T) { + name, err := GenerateRandomName("veth", 5) + if err != nil { + t.Fatal(err) + } + + expected := 5 + len("veth") + if len(name) != expected { + t.Fatalf("expected name to be %d chars but received %d", expected, len(name)) + } + + name, err = GenerateRandomName("veth", 65) + if err != nil { + t.Fatal(err) + } + + expected = 64 + len("veth") + if len(name) != expected { + t.Fatalf("expected name to be %d chars but received %d", expected, len(name)) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/errors.go b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/errors.go new file mode 100644 index 0000000..8cd7741 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/errors.go @@ -0,0 +1,8 @@ +package xattr + +import ( + "fmt" + "runtime" +) + +var ErrNotSupportedPlatform = fmt.Errorf("platform and architecture is not supported %s %s", runtime.GOOS, runtime.GOARCH) diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_linux.go b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_linux.go new file mode 100644 index 0000000..933a752 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_linux.go @@ -0,0 +1,53 @@ +// +build linux + +package xattr + +import ( + "syscall" + + "github.com/opencontainers/runc/libcontainer/system" +) + +func XattrEnabled(path string) bool { + if Setxattr(path, "user.test", "") == syscall.ENOTSUP { + return false + } + return true +} + +func stringsfromByte(buf []byte) (result []string) { + offset := 0 + for index, b := range buf { + if b == 0 { + result = append(result, string(buf[offset:index])) + offset = index + 1 + } + } + return +} + +func Listxattr(path string) ([]string, error) { + size, err := system.Llistxattr(path, nil) + if err != nil { + return nil, err + } + buf := make([]byte, size) + read, err := system.Llistxattr(path, buf) + if err != nil { + return nil, err + } + names := stringsfromByte(buf[:read]) + return names, nil +} + +func Getxattr(path, attr string) (string, error) { + value, err := system.Lgetxattr(path, attr) + if err != nil { + return "", err + } + return string(value), nil +} + +func Setxattr(path, xattr, value string) error { + return system.Lsetxattr(path, xattr, []byte(value), 0) +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_test.go b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_test.go new file mode 100644 index 0000000..1805568 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_test.go @@ -0,0 +1,78 @@ +// +build linux + +package xattr_test + +import ( + "os" + "testing" + + "github.com/opencontainers/runc/libcontainer/xattr" +) + +func TestXattr(t *testing.T) { + tmp := "xattr_test" + out, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE, 0) + if err != nil { + t.Fatal("failed") + } + defer os.Remove(tmp) + attr := "user.test" + out.Close() + + if !xattr.XattrEnabled(tmp) { + t.Log("Disabled") + t.Fatal("failed") + } + t.Log("Success") + + err = xattr.Setxattr(tmp, attr, "test") + if err != nil { + t.Fatal("failed") + } + + var value string + value, err = xattr.Getxattr(tmp, attr) + if err != nil { + t.Fatal("failed") + } + if value != "test" { + t.Fatal("failed") + } + t.Log("Success") + + var names []string + names, err = xattr.Listxattr(tmp) + if err != nil { + t.Fatal("failed") + } + + var found int + for _, name := range names { + if name == attr { + found = 1 + } + } + // Listxattr doesn't return trusted.* and system.* namespace + // attrs when run in unprevileged mode. + if found != 1 { + t.Fatal("failed") + } + t.Log("Success") + + big := "0000000000000000000000000000000000000000000000000000000000000000000008c6419ad822dfe29283fb3ac98dcc5908810cb31f4cfe690040c42c144b7492eicompslf20dxmlpgz" + // Test for long xattrs larger than 128 bytes + err = xattr.Setxattr(tmp, attr, big) + if err != nil { + t.Fatal("failed to add long value") + } + value, err = xattr.Getxattr(tmp, attr) + if err != nil { + t.Fatal("failed to get long value") + } + t.Log("Success") + + if value != big { + t.Fatal("failed, value doesn't match") + } + t.Log("Success") +} diff --git a/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_unsupported.go b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_unsupported.go new file mode 100644 index 0000000..821dea3 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/libcontainer/xattr/xattr_unsupported.go @@ -0,0 +1,15 @@ +// +build !linux + +package xattr + +func Listxattr(path string) ([]string, error) { + return nil, ErrNotSupportedPlatform +} + +func Getxattr(path, attr string) (string, error) { + return "", ErrNotSupportedPlatform +} + +func Setxattr(path, xattr, value string) error { + return ErrNotSupportedPlatform +} diff --git a/vendor/src/github.com/opencontainers/runc/list.go b/vendor/src/github.com/opencontainers/runc/list.go new file mode 100644 index 0000000..484d2bc --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/list.go @@ -0,0 +1,71 @@ +// +build linux + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/tabwriter" + "time" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" +) + +var listCommand = cli.Command{ + Name: "list", + Usage: "lists containers started by runc with the given root", + Action: func(context *cli.Context) { + factory, err := loadFactory(context) + if err != nil { + logrus.Fatal(err) + } + // get the list of containers + root := context.GlobalString("root") + absRoot, err := filepath.Abs(root) + if err != nil { + logrus.Fatal(err) + } + list, err := ioutil.ReadDir(absRoot) + if err != nil { + logrus.Fatal(err) + } + w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0) + fmt.Fprint(w, "ID\tPID\tSTATUS\tCREATED\n") + // output containers + for _, item := range list { + if item.IsDir() { + if err := outputListInfo(item.Name(), factory, w); err != nil { + logrus.Fatal(err) + } + } + } + if err := w.Flush(); err != nil { + logrus.Fatal(err) + } + }, +} + +func outputListInfo(id string, factory libcontainer.Factory, w *tabwriter.Writer) error { + container, err := factory.Load(id) + if err != nil { + return err + } + containerStatus, err := container.Status() + if err != nil { + return err + } + state, err := container.State() + if err != nil { + return err + } + fmt.Fprintf(w, "%s\t%d\t%s\t%s\n", + container.ID(), + state.BaseState.InitProcessPid, + containerStatus.String(), + state.BaseState.Created.Format(time.RFC3339Nano)) + return nil +} diff --git a/vendor/src/github.com/opencontainers/runc/main.go b/vendor/src/github.com/opencontainers/runc/main.go new file mode 100644 index 0000000..f6412f8 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "fmt" + "os" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/specs" +) + +const ( + version = "0.0.8" + specConfig = "config.json" + usage = `Open Container Initiative runtime + +runc is a command line client for running applications packaged according to +the Open Container Format (OCF) and is a compliant implementation of the +Open Container Initiative specification. + +runc integrates well with existing process supervisors to provide a production +container runtime environment for applications. It can be used with your +existing process monitoring tools and the container will be spawned as a +direct child of the process supervisor. + +After creating config files for your root filesystem with runc, you can execute +a container in your shell by running: + + # cd /mycontainer + # runc start [ -b bundle ] + +If not specified, the default value for the 'bundle' is the current directory. +'Bundle' is the directory where '` + specConfig + `' must be located.` +) + +func main() { + app := cli.NewApp() + app.Name = "runc" + app.Usage = usage + app.Version = fmt.Sprintf("%s\nspec version %s", version, specs.Version) + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + Usage: "enable debug output for logging", + }, + cli.StringFlag{ + Name: "log", + Usage: "set the log file path where internal debug information is written", + }, + cli.StringFlag{ + Name: "log-format", + Value: "text", + Usage: "set the format used by logs ('text' (default), or 'json')", + }, + cli.StringFlag{ + Name: "root", + Value: specs.LinuxStateDirectory, + Usage: "root directory for storage of container state (this should be located in tmpfs)", + }, + cli.StringFlag{ + Name: "criu", + Value: "criu", + Usage: "path to the criu binary used for checkpoint and restore", + }, + } + app.Commands = []cli.Command{ + checkpointCommand, + deleteCommand, + eventsCommand, + execCommand, + killCommand, + listCommand, + pauseCommand, + restoreCommand, + resumeCommand, + specCommand, + startCommand, + } + app.Before = func(context *cli.Context) error { + if context.GlobalBool("debug") { + logrus.SetLevel(logrus.DebugLevel) + } + if path := context.GlobalString("log"); path != "" { + f, err := os.Create(path) + if err != nil { + return err + } + logrus.SetOutput(f) + } + switch context.GlobalString("log-format") { + case "text": + // retain logrus's default. + case "json": + logrus.SetFormatter(new(logrus.JSONFormatter)) + default: + logrus.Fatalf("unknown log-format %q", context.GlobalString("log-format")) + } + return nil + } + if err := app.Run(os.Args); err != nil { + logrus.Fatal(err) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/main_unix.go b/vendor/src/github.com/opencontainers/runc/main_unix.go new file mode 100644 index 0000000..7bbec9f --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/main_unix.go @@ -0,0 +1,5 @@ +// +build linux + +package main + +import _ "github.com/opencontainers/runc/libcontainer/nsenter" diff --git a/vendor/src/github.com/opencontainers/runc/main_unsupported.go b/vendor/src/github.com/opencontainers/runc/main_unsupported.go new file mode 100644 index 0000000..6100120 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/main_unsupported.go @@ -0,0 +1,20 @@ +// +build !linux + +package main + +import ( + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) + +var ( + checkpointCommand cli.Command + eventsCommand cli.Command + restoreCommand cli.Command + specCommand cli.Command + killCommand cli.Command +) + +func runAction(*cli.Context) { + logrus.Fatal("Current OS is not supported yet") +} diff --git a/vendor/src/github.com/opencontainers/runc/pause.go b/vendor/src/github.com/opencontainers/runc/pause.go new file mode 100644 index 0000000..28545c5 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/pause.go @@ -0,0 +1,33 @@ +// +build linux + +package main + +import "github.com/codegangsta/cli" + +var pauseCommand = cli.Command{ + Name: "pause", + Usage: "pause suspends all processes inside the container", + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + if err := container.Pause(); err != nil { + fatal(err) + } + }, +} + +var resumeCommand = cli.Command{ + Name: "resume", + Usage: "resumes all processes that have been previously paused", + Action: func(context *cli.Context) { + container, err := getContainer(context) + if err != nil { + fatal(err) + } + if err := container.Resume(); err != nil { + fatal(err) + } + }, +} diff --git a/vendor/src/github.com/opencontainers/runc/restore.go b/vendor/src/github.com/opencontainers/runc/restore.go new file mode 100644 index 0000000..067c13d --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/restore.go @@ -0,0 +1,171 @@ +// +build linux + +package main + +import ( + "fmt" + "os" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/specs" +) + +var restoreCommand = cli.Command{ + Name: "restore", + Usage: "restore a container from a previous checkpoint", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "image-path", + Value: "", + Usage: "path to criu image files for restoring", + }, + cli.StringFlag{ + Name: "work-path", + Value: "", + Usage: "path for saving work files and logs", + }, + cli.BoolFlag{ + Name: "tcp-established", + Usage: "allow open tcp connections", + }, + cli.BoolFlag{ + Name: "ext-unix-sk", + Usage: "allow external unix sockets", + }, + cli.BoolFlag{ + Name: "shell-job", + Usage: "allow shell jobs", + }, + cli.BoolFlag{ + Name: "file-locks", + Usage: "handle file locks, for safety", + }, + cli.StringFlag{ + Name: "manage-cgroups-mode", + Value: "", + Usage: "cgroups mode: 'soft' (default), 'full' and 'strict'.", + }, + cli.StringFlag{ + Name: "bundle, b", + Value: "", + Usage: "path to the root of the bundle directory", + }, + cli.BoolFlag{ + Name: "detach,d", + Usage: "detach from the container's process", + }, + cli.StringFlag{ + Name: "pid-file", + Value: "", + Usage: "specify the file to write the process id to", + }, + }, + Action: func(context *cli.Context) { + imagePath := context.String("image-path") + id := context.Args().First() + if id == "" { + fatal(errEmptyID) + } + if imagePath == "" { + imagePath = getDefaultImagePath(context) + } + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + fatal(err) + } + } + spec, err := loadSpec(specConfig) + if err != nil { + fatal(err) + } + config, err := createLibcontainerConfig(id, spec) + if err != nil { + fatal(err) + } + status, err := restoreContainer(context, spec, config, imagePath) + if err != nil { + fatal(err) + } + os.Exit(status) + }, +} + +func restoreContainer(context *cli.Context, spec *specs.LinuxSpec, config *configs.Config, imagePath string) (code int, err error) { + var ( + rootuid = 0 + id = context.Args().First() + ) + factory, err := loadFactory(context) + if err != nil { + return -1, err + } + container, err := factory.Load(id) + if err != nil { + container, err = factory.Create(id, config) + if err != nil { + return -1, err + } + } + options := criuOptions(context) + + status, err := container.Status() + if err != nil { + logrus.Error(err) + } + if status == libcontainer.Running { + fatal(fmt.Errorf("Container with id %s already running", id)) + } + + setManageCgroupsMode(context, options) + + // ensure that the container is always removed if we were the process + // that created it. + detach := context.Bool("detach") + if !detach { + defer destroy(container) + } + process := &libcontainer.Process{} + tty, err := setupIO(process, rootuid, "", false, detach) + if err != nil { + return -1, err + } + if err := container.Restore(process, options); err != nil { + tty.Close() + return -1, err + } + if pidFile := context.String("pid-file"); pidFile != "" { + if err := createPidFile(pidFile, process); err != nil { + process.Signal(syscall.SIGKILL) + process.Wait() + tty.Close() + return -1, err + } + } + if detach { + return 0, nil + } + handler := newSignalHandler(tty) + defer handler.Close() + return handler.forward(process) +} + +func criuOptions(context *cli.Context) *libcontainer.CriuOpts { + imagePath := getCheckpointImagePath(context) + if err := os.MkdirAll(imagePath, 0655); err != nil { + fatal(err) + } + return &libcontainer.CriuOpts{ + ImagesDirectory: imagePath, + WorkDirectory: context.String("work-path"), + LeaveRunning: context.Bool("leave-running"), + TcpEstablished: context.Bool("tcp-established"), + ExternalUnixConnections: context.Bool("ext-unix-sk"), + ShellJob: context.Bool("shell-job"), + FileLocks: context.Bool("file-locks"), + } +} diff --git a/vendor/src/github.com/opencontainers/runc/rlimit_linux.go b/vendor/src/github.com/opencontainers/runc/rlimit_linux.go new file mode 100644 index 0000000..a296828 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/rlimit_linux.go @@ -0,0 +1,49 @@ +package main + +import "fmt" + +const ( + RLIMIT_CPU = iota // CPU time in sec + RLIMIT_FSIZE // Maximum filesize + RLIMIT_DATA // max data size + RLIMIT_STACK // max stack size + RLIMIT_CORE // max core file size + RLIMIT_RSS // max resident set size + RLIMIT_NPROC // max number of processes + RLIMIT_NOFILE // max number of open files + RLIMIT_MEMLOCK // max locked-in-memory address space + RLIMIT_AS // address space limit + RLIMIT_LOCKS // maximum file locks held + RLIMIT_SIGPENDING // max number of pending signals + RLIMIT_MSGQUEUE // maximum bytes in POSIX mqueues + RLIMIT_NICE // max nice prio allowed to raise to + RLIMIT_RTPRIO // maximum realtime priority + RLIMIT_RTTIME // timeout for RT tasks in us +) + +var rlimitMap = map[string]int{ + "RLIMIT_CPU": RLIMIT_CPU, + "RLIMIT_FSIZE": RLIMIT_FSIZE, + "RLIMIT_DATA": RLIMIT_DATA, + "RLIMIT_STACK": RLIMIT_STACK, + "RLIMIT_CORE": RLIMIT_CORE, + "RLIMIT_RSS": RLIMIT_RSS, + "RLIMIT_NPROC": RLIMIT_NPROC, + "RLIMIT_NOFILE": RLIMIT_NOFILE, + "RLIMIT_MEMLOCK": RLIMIT_MEMLOCK, + "RLIMIT_AS": RLIMIT_AS, + "RLIMIT_LOCKS": RLIMIT_LOCKS, + "RLIMIT_SGPENDING": RLIMIT_SIGPENDING, + "RLIMIT_MSGQUEUE": RLIMIT_MSGQUEUE, + "RLIMIT_NICE": RLIMIT_NICE, + "RLIMIT_RTPRIO": RLIMIT_RTPRIO, + "RLIMIT_RTTIME": RLIMIT_RTTIME, +} + +func strToRlimit(key string) (int, error) { + rl, ok := rlimitMap[key] + if !ok { + return 0, fmt.Errorf("Wrong rlimit value: %s", key) + } + return rl, nil +} diff --git a/vendor/src/github.com/opencontainers/runc/script/.validate b/vendor/src/github.com/opencontainers/runc/script/.validate new file mode 100644 index 0000000..170d674 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/script/.validate @@ -0,0 +1,33 @@ +#!/bin/bash + +if [ -z "$VALIDATE_UPSTREAM" ]; then + # this is kind of an expensive check, so let's not do this twice if we + # are running more than one validate bundlescript + + VALIDATE_REPO='https://github.com/opencontainers/runc.git' + VALIDATE_BRANCH='master' + + if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then + VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git" + VALIDATE_BRANCH="${TRAVIS_BRANCH}" + fi + + VALIDATE_HEAD="$(git rev-parse --verify HEAD)" + + git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH" + VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)" + + VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD" + VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD" + + validate_diff() { + if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then + git diff "$VALIDATE_COMMIT_DIFF" "$@" + fi + } + validate_log() { + if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then + git log "$VALIDATE_COMMIT_LOG" "$@" + fi + } +fi diff --git a/vendor/src/github.com/opencontainers/runc/script/test_Dockerfile b/vendor/src/github.com/opencontainers/runc/script/test_Dockerfile new file mode 100644 index 0000000..2fe7358 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/script/test_Dockerfile @@ -0,0 +1,32 @@ +FROM golang:1.5.3 + +RUN echo "deb http://ftp.us.debian.org/debian testing main contrib" >> /etc/apt/sources.list +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + iptables \ + libaio-dev \ + libcap-dev \ + libprotobuf-dev \ + libprotobuf-c0-dev \ + libseccomp2 \ + libseccomp-dev \ + protobuf-c-compiler \ + protobuf-compiler \ + python-minimal \ + --no-install-recommends + +# install criu +ENV CRIU_VERSION 1.7 +RUN mkdir -p /usr/src/criu \ + && curl -sSL https://github.com/xemul/criu/archive/v${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1 \ + && cd /usr/src/criu \ + && make install-criu + +# setup a playground for us to spawn containers in +RUN mkdir /busybox && \ + curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox + +COPY script/tmpmount / +WORKDIR /go/src/github.com/opencontainers/runc +ENTRYPOINT ["/tmpmount"] diff --git a/vendor/src/github.com/opencontainers/runc/script/tmpmount b/vendor/src/github.com/opencontainers/runc/script/tmpmount new file mode 100755 index 0000000..5ac6bc2 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/script/tmpmount @@ -0,0 +1,4 @@ +#!/bin/bash + +mount -t tmpfs none /tmp +exec "$@" diff --git a/vendor/src/github.com/opencontainers/runc/script/validate-gofmt b/vendor/src/github.com/opencontainers/runc/script/validate-gofmt new file mode 100755 index 0000000..c565976 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/script/validate-gofmt @@ -0,0 +1,30 @@ +#!/bin/bash + +source "$(dirname "$BASH_SOURCE")/.validate" + +IFS=$'\n' +files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^Godeps/' || true) ) +unset IFS + +badFiles=() +for f in "${files[@]}"; do + # we use "git show" here to validate that what's committed is formatted + if [ "$(git show "$VALIDATE_HEAD:$f" | gofmt -s -l)" ]; then + badFiles+=( "$f" ) + fi +done + +if [ ${#badFiles[@]} -eq 0 ]; then + echo 'Congratulations! All Go source files are properly formatted.' +else + { + echo "These files are not properly gofmt'd:" + for f in "${badFiles[@]}"; do + echo " - $f" + done + echo + echo 'Please reformat the above files using "gofmt -s -w" and commit the result.' + echo + } >&2 + false +fi diff --git a/vendor/src/github.com/opencontainers/runc/signals.go b/vendor/src/github.com/opencontainers/runc/signals.go new file mode 100644 index 0000000..3a06d29 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/signals.go @@ -0,0 +1,113 @@ +// +build linux + +package main + +import ( + "os" + "os/signal" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/utils" +) + +const signalBufferSize = 2048 + +// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals +// while still forwarding all other signals to the process. +func newSignalHandler(tty *tty) *signalHandler { + // ensure that we have a large buffer size so that we do not miss any signals + // incase we are not processing them fast enough. + s := make(chan os.Signal, signalBufferSize) + // handle all signals for the process. + signal.Notify(s) + return &signalHandler{ + tty: tty, + signals: s, + } +} + +// exit models a process exit status with the pid and +// exit status. +type exit struct { + pid int + status int +} + +type signalHandler struct { + signals chan os.Signal + tty *tty +} + +// forward handles the main signal event loop forwarding, resizing, or reaping depending +// on the signal received. +func (h *signalHandler) forward(process *libcontainer.Process) (int, error) { + // make sure we know the pid of our main process so that we can return + // after it dies. + pid1, err := process.Pid() + if err != nil { + return -1, err + } + // perform the initial tty resize. + h.tty.resize() + for s := range h.signals { + switch s { + case syscall.SIGWINCH: + h.tty.resize() + case syscall.SIGCHLD: + exits, err := h.reap() + if err != nil { + logrus.Error(err) + } + for _, e := range exits { + logrus.WithFields(logrus.Fields{ + "pid": e.pid, + "status": e.status, + }).Debug("process exited") + if e.pid == pid1 { + // call Wait() on the process even though we already have the exit + // status because we must ensure that any of the go specific process + // fun such as flushing pipes are complete before we return. + process.Wait() + return e.status, nil + } + } + default: + logrus.Debugf("sending signal to process %s", s) + if err := syscall.Kill(pid1, s.(syscall.Signal)); err != nil { + logrus.Error(err) + } + } + } + return -1, nil +} + +// reap runs wait4 in a loop until we have finished processing any existing exits +// then returns all exits to the main event loop for further processing. +func (h *signalHandler) reap() (exits []exit, err error) { + var ( + ws syscall.WaitStatus + rus syscall.Rusage + ) + for { + pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus) + if err != nil { + if err == syscall.ECHILD { + return exits, nil + } + return nil, err + } + if pid <= 0 { + return exits, nil + } + exits = append(exits, exit{ + pid: pid, + status: utils.ExitStatus(ws), + }) + } +} + +func (h *signalHandler) Close() error { + return h.tty.Close() +} diff --git a/vendor/src/github.com/opencontainers/runc/spec.go b/vendor/src/github.com/opencontainers/runc/spec.go new file mode 100644 index 0000000..2836e7c --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/spec.go @@ -0,0 +1,783 @@ +// +build linux + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/seccomp" + libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" + "github.com/opencontainers/specs" +) + +var specCommand = cli.Command{ + Name: "spec", + Usage: "create a new specification file", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "bundle, b", + Value: "", + Usage: "path to the root of the bundle directory", + }, + }, + Action: func(context *cli.Context) { + spec := specs.LinuxSpec{ + Spec: specs.Spec{ + Version: specs.Version, + Platform: specs.Platform{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + }, + Root: specs.Root{ + Path: "rootfs", + Readonly: true, + }, + Process: specs.Process{ + Terminal: true, + User: specs.User{}, + Args: []string{ + "sh", + }, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + }, + Cwd: "/", + }, + Hostname: "shell", + Mounts: []specs.Mount{ + { + Destination: "/proc", + Type: "proc", + Source: "proc", + Options: nil, + }, + { + Destination: "/dev", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + { + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, + }, + { + Destination: "/dev/shm", + Type: "tmpfs", + Source: "shm", + Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}, + }, + { + Destination: "/dev/mqueue", + Type: "mqueue", + Source: "mqueue", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/sys", + Type: "sysfs", + Source: "sysfs", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"}, + }, + }, + }, + Linux: specs.Linux{ + Capabilities: []string{ + "CAP_AUDIT_WRITE", + "CAP_KILL", + "CAP_NET_BIND_SERVICE", + }, + Resources: &specs.Resources{ + Devices: []specs.DeviceCgroup{ + { + Allow: false, + Access: sPtr("rwm"), + }, + }, + }, + Namespaces: []specs.Namespace{ + { + Type: "pid", + }, + { + Type: "network", + }, + { + Type: "ipc", + }, + { + Type: "uts", + }, + { + Type: "mount", + }, + }, + Rlimits: []specs.Rlimit{ + { + Type: "RLIMIT_NOFILE", + Hard: uint64(1024), + Soft: uint64(1024), + }, + }, + }, + } + + checkNoFile := func(name string) error { + _, err := os.Stat(name) + if err == nil { + return fmt.Errorf("File %s exists. Remove it first", name) + } + if !os.IsNotExist(err) { + return err + } + return nil + } + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + fatal(err) + } + } + if err := checkNoFile(specConfig); err != nil { + logrus.Fatal(err) + } + data, err := json.MarshalIndent(&spec, "", "\t") + if err != nil { + logrus.Fatal(err) + } + if err := ioutil.WriteFile(specConfig, data, 0666); err != nil { + logrus.Fatal(err) + } + }, +} + +func sPtr(s string) *string { return &s } +func rPtr(r rune) *rune { return &r } +func iPtr(i int64) *int64 { return &i } +func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } +func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } + +var namespaceMapping = map[specs.NamespaceType]configs.NamespaceType{ + specs.PIDNamespace: configs.NEWPID, + specs.NetworkNamespace: configs.NEWNET, + specs.MountNamespace: configs.NEWNS, + specs.UserNamespace: configs.NEWUSER, + specs.IPCNamespace: configs.NEWIPC, + specs.UTSNamespace: configs.NEWUTS, +} + +var mountPropagationMapping = map[string]int{ + "rprivate": syscall.MS_PRIVATE | syscall.MS_REC, + "private": syscall.MS_PRIVATE, + "rslave": syscall.MS_SLAVE | syscall.MS_REC, + "slave": syscall.MS_SLAVE, + "rshared": syscall.MS_SHARED | syscall.MS_REC, + "shared": syscall.MS_SHARED, + "": syscall.MS_PRIVATE | syscall.MS_REC, +} + +// validateSpec validates the fields in the spec +// TODO: Add validation for other fields where applicable +func validateSpec(spec *specs.LinuxSpec) error { + if spec.Process.Cwd == "" { + return fmt.Errorf("Cwd property must not be empty") + } + if !filepath.IsAbs(spec.Process.Cwd) { + return fmt.Errorf("Cwd must be an absolute path") + } + return nil +} + +// loadSpec loads the specification from the provided path. +// If the path is empty then the default path will be "config.json" +func loadSpec(cPath string) (spec *specs.LinuxSpec, err error) { + cf, err := os.Open(cPath) + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("JSON specification file %s not found", cPath) + } + return spec, err + } + defer cf.Close() + + if err = json.NewDecoder(cf).Decode(&spec); err != nil { + return spec, err + } + return spec, validateSpec(spec) +} + +func createLibcontainerConfig(cgroupName string, spec *specs.LinuxSpec) (*configs.Config, error) { + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + rootfsPath := spec.Root.Path + if !filepath.IsAbs(rootfsPath) { + rootfsPath = filepath.Join(cwd, rootfsPath) + } + config := &configs.Config{ + Rootfs: rootfsPath, + Capabilities: spec.Linux.Capabilities, + Readonlyfs: spec.Root.Readonly, + Hostname: spec.Hostname, + } + + exists := false + if config.RootPropagation, exists = mountPropagationMapping[spec.Linux.RootfsPropagation]; !exists { + return nil, fmt.Errorf("rootfsPropagation=%v is not supported", spec.Linux.RootfsPropagation) + } + + for _, ns := range spec.Linux.Namespaces { + t, exists := namespaceMapping[ns.Type] + if !exists { + return nil, fmt.Errorf("namespace %q does not exist", ns) + } + config.Namespaces.Add(t, ns.Path) + } + if config.Namespaces.Contains(configs.NEWNET) { + config.Networks = []*configs.Network{ + { + Type: "loopback", + }, + } + } + for _, m := range spec.Mounts { + config.Mounts = append(config.Mounts, createLibcontainerMount(cwd, m)) + } + if err := createDevices(spec, config); err != nil { + return nil, err + } + if err := setupUserNamespace(spec, config); err != nil { + return nil, err + } + for _, rlimit := range spec.Linux.Rlimits { + rl, err := createLibContainerRlimit(rlimit) + if err != nil { + return nil, err + } + config.Rlimits = append(config.Rlimits, rl) + } + c, err := createCgroupConfig(cgroupName, spec) + if err != nil { + return nil, err + } + config.Cgroups = c + if config.Readonlyfs { + setReadonly(config) + config.MaskPaths = []string{ + "/proc/kcore", + } + config.ReadonlyPaths = []string{ + "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", + } + } + seccomp, err := setupSeccomp(&spec.Linux.Seccomp) + if err != nil { + return nil, err + } + config.Seccomp = seccomp + config.Sysctl = spec.Linux.Sysctl + config.ProcessLabel = spec.Linux.SelinuxProcessLabel + config.AppArmorProfile = spec.Linux.ApparmorProfile + for _, g := range spec.Process.User.AdditionalGids { + config.AdditionalGroups = append(config.AdditionalGroups, strconv.FormatUint(uint64(g), 10)) + } + createHooks(spec, config) + config.Version = specs.Version + return config, nil +} + +func createLibcontainerMount(cwd string, m specs.Mount) *configs.Mount { + flags, pgflags, data := parseMountOptions(m.Options) + source := m.Source + if m.Type == "bind" { + if !filepath.IsAbs(source) { + source = filepath.Join(cwd, m.Source) + } + } + return &configs.Mount{ + Device: m.Type, + Source: source, + Destination: m.Destination, + Data: data, + Flags: flags, + PropagationFlags: pgflags, + } +} + +func createCgroupConfig(name string, spec *specs.LinuxSpec) (*configs.Cgroup, error) { + var ( + err error + myCgroupPath string + ) + + if spec.Linux.CgroupsPath != nil { + myCgroupPath = libcontainerUtils.CleanPath(*spec.Linux.CgroupsPath) + } else { + myCgroupPath, err = cgroups.GetThisCgroupDir("devices") + if err != nil { + return nil, err + } + } + + c := &configs.Cgroup{ + Path: filepath.Join(myCgroupPath, name), + Resources: &configs.Resources{}, + } + c.Resources.AllowedDevices = allowedDevices + r := spec.Linux.Resources + if r == nil { + return c, nil + } + for i, d := range spec.Linux.Resources.Devices { + var ( + t = 'a' + major = int64(-1) + minor = int64(-1) + ) + if d.Type != nil { + t = *d.Type + } + if d.Major != nil { + major = *d.Major + } + if d.Minor != nil { + minor = *d.Minor + } + if d.Access == nil || *d.Access == "" { + return nil, fmt.Errorf("device access at %d field canot be empty", i) + } + dd := &configs.Device{ + Type: t, + Major: major, + Minor: minor, + Permissions: *d.Access, + Allow: d.Allow, + } + c.Resources.Devices = append(c.Resources.Devices, dd) + } + // append the default allowed devices to the end of the list + c.Resources.Devices = append(c.Resources.Devices, allowedDevices...) + if r.Memory != nil { + if r.Memory.Limit != nil { + c.Resources.Memory = int64(*r.Memory.Limit) + } + if r.Memory.Reservation != nil { + c.Resources.MemoryReservation = int64(*r.Memory.Reservation) + } + if r.Memory.Swap != nil { + c.Resources.MemorySwap = int64(*r.Memory.Swap) + } + if r.Memory.Kernel != nil { + c.Resources.KernelMemory = int64(*r.Memory.Kernel) + } + if r.Memory.Swappiness != nil { + c.Resources.MemorySwappiness = int64(*r.Memory.Swappiness) + } + } + if r.CPU != nil { + if r.CPU.Shares != nil { + c.Resources.CpuShares = int64(*r.CPU.Shares) + } + if r.CPU.Quota != nil { + c.Resources.CpuQuota = int64(*r.CPU.Quota) + } + if r.CPU.Period != nil { + c.Resources.CpuPeriod = int64(*r.CPU.Period) + } + if r.CPU.RealtimeRuntime != nil { + c.Resources.CpuRtRuntime = int64(*r.CPU.RealtimeRuntime) + } + if r.CPU.RealtimePeriod != nil { + c.Resources.CpuRtPeriod = int64(*r.CPU.RealtimePeriod) + } + if r.CPU.Cpus != nil { + c.Resources.CpusetCpus = *r.CPU.Cpus + } + if r.CPU.Mems != nil { + c.Resources.CpusetMems = *r.CPU.Mems + } + } + if r.Pids != nil { + c.Resources.PidsLimit = *r.Pids.Limit + } + if r.BlockIO != nil { + if r.BlockIO.Weight != nil { + c.Resources.BlkioWeight = *r.BlockIO.Weight + } + if r.BlockIO.LeafWeight != nil { + c.Resources.BlkioLeafWeight = *r.BlockIO.LeafWeight + } + if r.BlockIO.WeightDevice != nil { + for _, wd := range r.BlockIO.WeightDevice { + weightDevice := configs.NewWeightDevice(wd.Major, wd.Minor, *wd.Weight, *wd.LeafWeight) + c.Resources.BlkioWeightDevice = append(c.Resources.BlkioWeightDevice, weightDevice) + } + } + if r.BlockIO.ThrottleReadBpsDevice != nil { + for _, td := range r.BlockIO.ThrottleReadBpsDevice { + throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) + c.Resources.BlkioThrottleReadBpsDevice = append(c.Resources.BlkioThrottleReadBpsDevice, throttleDevice) + } + } + if r.BlockIO.ThrottleWriteBpsDevice != nil { + for _, td := range r.BlockIO.ThrottleWriteBpsDevice { + throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) + c.Resources.BlkioThrottleWriteBpsDevice = append(c.Resources.BlkioThrottleWriteBpsDevice, throttleDevice) + } + } + if r.BlockIO.ThrottleReadIOPSDevice != nil { + for _, td := range r.BlockIO.ThrottleReadIOPSDevice { + throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) + c.Resources.BlkioThrottleReadIOPSDevice = append(c.Resources.BlkioThrottleReadIOPSDevice, throttleDevice) + } + } + if r.BlockIO.ThrottleWriteIOPSDevice != nil { + for _, td := range r.BlockIO.ThrottleWriteIOPSDevice { + throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, *td.Rate) + c.Resources.BlkioThrottleWriteIOPSDevice = append(c.Resources.BlkioThrottleWriteIOPSDevice, throttleDevice) + } + } + } + for _, l := range r.HugepageLimits { + c.Resources.HugetlbLimit = append(c.Resources.HugetlbLimit, &configs.HugepageLimit{ + Pagesize: *l.Pagesize, + Limit: *l.Limit, + }) + } + if r.DisableOOMKiller != nil { + c.Resources.OomKillDisable = *r.DisableOOMKiller + } + if r.Network != nil { + if r.Network.ClassID != nil { + c.Resources.NetClsClassid = string(*r.Network.ClassID) + } + for _, m := range r.Network.Priorities { + c.Resources.NetPrioIfpriomap = append(c.Resources.NetPrioIfpriomap, &configs.IfPrioMap{ + Interface: m.Name, + Priority: int64(m.Priority), + }) + } + } + return c, nil +} + +func createDevices(spec *specs.LinuxSpec, config *configs.Config) error { + // add whitelisted devices + config.Devices = []*configs.Device{ + { + Type: 'c', + Path: "/dev/null", + Major: 1, + Minor: 3, + FileMode: 0666, + Uid: 0, + Gid: 0, + }, + { + Type: 'c', + Path: "/dev/random", + Major: 1, + Minor: 8, + FileMode: 0666, + Uid: 0, + Gid: 0, + }, + { + Type: 'c', + Path: "/dev/full", + Major: 1, + Minor: 7, + FileMode: 0666, + Uid: 0, + Gid: 0, + }, + { + Type: 'c', + Path: "/dev/tty", + Major: 5, + Minor: 0, + FileMode: 0666, + Uid: 0, + Gid: 0, + }, + { + Type: 'c', + Path: "/dev/zero", + Major: 1, + Minor: 5, + FileMode: 0666, + Uid: 0, + Gid: 0, + }, + { + Type: 'c', + Path: "/dev/urandom", + Major: 1, + Minor: 9, + FileMode: 0666, + Uid: 0, + Gid: 0, + }, + } + // merge in additional devices from the spec + for _, d := range spec.Linux.Devices { + var uid, gid uint32 + if d.UID != nil { + uid = *d.UID + } + if d.GID != nil { + gid = *d.GID + } + device := &configs.Device{ + Type: d.Type, + Path: d.Path, + Major: d.Major, + Minor: d.Minor, + FileMode: *d.FileMode, + Uid: uid, + Gid: gid, + } + config.Devices = append(config.Devices, device) + } + return nil +} + +func setReadonly(config *configs.Config) { + for _, m := range config.Mounts { + if m.Device == "sysfs" { + m.Flags |= syscall.MS_RDONLY + } + } +} + +func setupUserNamespace(spec *specs.LinuxSpec, config *configs.Config) error { + if len(spec.Linux.UIDMappings) == 0 { + return nil + } + config.Namespaces.Add(configs.NEWUSER, "") + create := func(m specs.IDMapping) configs.IDMap { + return configs.IDMap{ + HostID: int(m.HostID), + ContainerID: int(m.ContainerID), + Size: int(m.Size), + } + } + for _, m := range spec.Linux.UIDMappings { + config.UidMappings = append(config.UidMappings, create(m)) + } + for _, m := range spec.Linux.GIDMappings { + config.GidMappings = append(config.GidMappings, create(m)) + } + rootUID, err := config.HostUID() + if err != nil { + return err + } + rootGID, err := config.HostGID() + if err != nil { + return err + } + for _, node := range config.Devices { + node.Uid = uint32(rootUID) + node.Gid = uint32(rootGID) + } + return nil +} + +func createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) { + rl, err := strToRlimit(rlimit.Type) + if err != nil { + return configs.Rlimit{}, err + } + return configs.Rlimit{ + Type: rl, + Hard: uint64(rlimit.Hard), + Soft: uint64(rlimit.Soft), + }, nil +} + +// parseMountOptions parses the string and returns the flags, propagation +// flags and any mount data that it contains. +func parseMountOptions(options []string) (int, []int, string) { + var ( + flag int + pgflag []int + data []string + ) + flags := map[string]struct { + clear bool + flag int + }{ + "async": {true, syscall.MS_SYNCHRONOUS}, + "atime": {true, syscall.MS_NOATIME}, + "bind": {false, syscall.MS_BIND}, + "defaults": {false, 0}, + "dev": {true, syscall.MS_NODEV}, + "diratime": {true, syscall.MS_NODIRATIME}, + "dirsync": {false, syscall.MS_DIRSYNC}, + "exec": {true, syscall.MS_NOEXEC}, + "mand": {false, syscall.MS_MANDLOCK}, + "noatime": {false, syscall.MS_NOATIME}, + "nodev": {false, syscall.MS_NODEV}, + "nodiratime": {false, syscall.MS_NODIRATIME}, + "noexec": {false, syscall.MS_NOEXEC}, + "nomand": {true, syscall.MS_MANDLOCK}, + "norelatime": {true, syscall.MS_RELATIME}, + "nostrictatime": {true, syscall.MS_STRICTATIME}, + "nosuid": {false, syscall.MS_NOSUID}, + "rbind": {false, syscall.MS_BIND | syscall.MS_REC}, + "relatime": {false, syscall.MS_RELATIME}, + "remount": {false, syscall.MS_REMOUNT}, + "ro": {false, syscall.MS_RDONLY}, + "rw": {true, syscall.MS_RDONLY}, + "strictatime": {false, syscall.MS_STRICTATIME}, + "suid": {true, syscall.MS_NOSUID}, + "sync": {false, syscall.MS_SYNCHRONOUS}, + } + propagationFlags := map[string]struct { + clear bool + flag int + }{ + "private": {false, syscall.MS_PRIVATE}, + "shared": {false, syscall.MS_SHARED}, + "slave": {false, syscall.MS_SLAVE}, + "unbindable": {false, syscall.MS_UNBINDABLE}, + "rprivate": {false, syscall.MS_PRIVATE | syscall.MS_REC}, + "rshared": {false, syscall.MS_SHARED | syscall.MS_REC}, + "rslave": {false, syscall.MS_SLAVE | syscall.MS_REC}, + "runbindable": {false, syscall.MS_UNBINDABLE | syscall.MS_REC}, + } + for _, o := range options { + // If the option does not exist in the flags table or the flag + // is not supported on the platform, + // then it is a data value for a specific fs type + if f, exists := flags[o]; exists && f.flag != 0 { + if f.clear { + flag &= ^f.flag + } else { + flag |= f.flag + } + } else if f, exists := propagationFlags[o]; exists && f.flag != 0 { + pgflag = append(pgflag, f.flag) + } else { + data = append(data, o) + } + } + return flag, pgflag, strings.Join(data, ",") +} + +func setupSeccomp(config *specs.Seccomp) (*configs.Seccomp, error) { + if config == nil { + return nil, nil + } + + // No default action specified, no syscalls listed, assume seccomp disabled + if config.DefaultAction == "" && len(config.Syscalls) == 0 { + return nil, nil + } + + newConfig := new(configs.Seccomp) + newConfig.Syscalls = []*configs.Syscall{} + + if len(config.Architectures) > 0 { + newConfig.Architectures = []string{} + for _, arch := range config.Architectures { + newArch, err := seccomp.ConvertStringToArch(string(arch)) + if err != nil { + return nil, err + } + newConfig.Architectures = append(newConfig.Architectures, newArch) + } + } + + // Convert default action from string representation + newDefaultAction, err := seccomp.ConvertStringToAction(string(config.DefaultAction)) + if err != nil { + return nil, err + } + newConfig.DefaultAction = newDefaultAction + + // Loop through all syscall blocks and convert them to libcontainer format + for _, call := range config.Syscalls { + newAction, err := seccomp.ConvertStringToAction(string(call.Action)) + if err != nil { + return nil, err + } + + newCall := configs.Syscall{ + Name: call.Name, + Action: newAction, + Args: []*configs.Arg{}, + } + + // Loop through all the arguments of the syscall and convert them + for _, arg := range call.Args { + newOp, err := seccomp.ConvertStringToOperator(string(arg.Op)) + if err != nil { + return nil, err + } + + newArg := configs.Arg{ + Index: arg.Index, + Value: arg.Value, + ValueTwo: arg.ValueTwo, + Op: newOp, + } + + newCall.Args = append(newCall.Args, &newArg) + } + + newConfig.Syscalls = append(newConfig.Syscalls, &newCall) + } + + return newConfig, nil +} + +func createHooks(rspec *specs.LinuxSpec, config *configs.Config) { + config.Hooks = &configs.Hooks{} + for _, h := range rspec.Hooks.Prestart { + cmd := configs.Command{ + Path: h.Path, + Args: h.Args, + Env: h.Env, + } + config.Hooks.Prestart = append(config.Hooks.Prestart, configs.NewCommandHook(cmd)) + } + for _, h := range rspec.Hooks.Poststart { + cmd := configs.Command{ + Path: h.Path, + Args: h.Args, + Env: h.Env, + } + config.Hooks.Poststart = append(config.Hooks.Poststart, configs.NewCommandHook(cmd)) + } + for _, h := range rspec.Hooks.Poststop { + cmd := configs.Command{ + Path: h.Path, + Args: h.Args, + Env: h.Env, + } + config.Hooks.Poststop = append(config.Hooks.Poststop, configs.NewCommandHook(cmd)) + } +} diff --git a/vendor/src/github.com/opencontainers/runc/start.go b/vendor/src/github.com/opencontainers/runc/start.go new file mode 100644 index 0000000..6d8238e --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/start.go @@ -0,0 +1,108 @@ +// +build linux + +package main + +import ( + "os" + "runtime" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/coreos/go-systemd/activation" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/specs" +) + +// default action is to start a container +var startCommand = cli.Command{ + Name: "start", + Usage: "create and run a container", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "bundle, b", + Value: "", + Usage: "path to the root of the bundle directory", + }, + cli.StringFlag{ + Name: "console", + Value: "", + Usage: "specify the pty slave path for use with the container", + }, + cli.BoolFlag{ + Name: "detach,d", + Usage: "detach from the container's process", + }, + cli.StringFlag{ + Name: "pid-file", + Value: "", + Usage: "specify the file to write the process id to", + }, + }, + Action: func(context *cli.Context) { + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + fatal(err) + } + } + spec, err := loadSpec(specConfig) + if err != nil { + fatal(err) + } + + notifySocket := os.Getenv("NOTIFY_SOCKET") + if notifySocket != "" { + setupSdNotify(spec, notifySocket) + } + + if os.Geteuid() != 0 { + logrus.Fatal("runc should be run as root") + } + + status, err := startContainer(context, spec) + if err != nil { + logrus.Fatalf("Container start failed: %v", err) + } + // exit with the container's exit status so any external supervisor is + // notified of the exit with the correct exit status. + os.Exit(status) + }, +} + +func init() { + if len(os.Args) > 1 && os.Args[1] == "init" { + runtime.GOMAXPROCS(1) + runtime.LockOSThread() + factory, _ := libcontainer.New("") + if err := factory.StartInitialization(); err != nil { + fatal(err) + } + panic("--this line should have never been executed, congratulations--") + } +} + +func startContainer(context *cli.Context, spec *specs.LinuxSpec) (int, error) { + id := context.Args().First() + if id == "" { + return -1, errEmptyID + } + container, err := createContainer(context, id, spec) + if err != nil { + return -1, err + } + + // ensure that the container is always removed if we were the process + // that created it. + detach := context.Bool("detach") + if !detach { + defer destroy(container) + } + + // Support on-demand socket activation by passing file descriptors into the container init process. + listenFDs := []*os.File{} + if os.Getenv("LISTEN_FDS") != "" { + listenFDs = activation.Files(false) + } + + return runProcess(container, &spec.Process, listenFDs, context.String("console"), context.String("pid-file"), detach) +} diff --git a/vendor/src/github.com/opencontainers/runc/tty.go b/vendor/src/github.com/opencontainers/runc/tty.go new file mode 100644 index 0000000..b552621 --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/tty.go @@ -0,0 +1,100 @@ +// +build linux + +package main + +import ( + "fmt" + "io" + "os" + + "github.com/docker/docker/pkg/term" + "github.com/opencontainers/runc/libcontainer" +) + +// newTty creates a new tty for use with the container. If a tty is not to be +// created for the process, pipes are created so that the TTY of the parent +// process are not inherited by the container. +func newTty(create bool, p *libcontainer.Process, rootuid int, console string) (*tty, error) { + if create { + return createTty(p, rootuid, console) + } + return createStdioPipes(p, rootuid) +} + +// setup standard pipes so that the TTY of the calling runc process +// is not inherited by the container. +func createStdioPipes(p *libcontainer.Process, rootuid int) (*tty, error) { + i, err := p.InitializeIO(rootuid) + if err != nil { + return nil, err + } + t := &tty{ + closers: []io.Closer{ + i.Stdin, + i.Stdout, + i.Stderr, + }, + } + go func() { + io.Copy(i.Stdin, os.Stdin) + i.Stdin.Close() + }() + go io.Copy(os.Stdout, i.Stdout) + go io.Copy(os.Stderr, i.Stderr) + return t, nil +} + +func createTty(p *libcontainer.Process, rootuid int, consolePath string) (*tty, error) { + if consolePath != "" { + if err := p.ConsoleFromPath(consolePath); err != nil { + return nil, err + } + return &tty{}, nil + } + console, err := p.NewConsole(rootuid) + if err != nil { + return nil, err + } + go io.Copy(console, os.Stdin) + go io.Copy(os.Stdout, console) + + state, err := term.SetRawTerminal(os.Stdin.Fd()) + if err != nil { + return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err) + } + t := &tty{ + console: console, + state: state, + closers: []io.Closer{ + console, + }, + } + return t, nil +} + +type tty struct { + console libcontainer.Console + state *term.State + closers []io.Closer +} + +func (t *tty) Close() error { + for _, c := range t.closers { + c.Close() + } + if t.state != nil { + term.RestoreTerminal(os.Stdin.Fd(), t.state) + } + return nil +} + +func (t *tty) resize() error { + if t.console == nil { + return nil + } + ws, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + return err + } + return term.SetWinsize(t.console.Fd(), ws) +} diff --git a/vendor/src/github.com/opencontainers/runc/utils.go b/vendor/src/github.com/opencontainers/runc/utils.go new file mode 100644 index 0000000..fe52b1b --- /dev/null +++ b/vendor/src/github.com/opencontainers/runc/utils.go @@ -0,0 +1,330 @@ +// +build linux + +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/specs" +) + +const wildcard = -1 + +var errEmptyID = errors.New("container id cannot be empty") + +var allowedDevices = []*configs.Device{ + // allow mknod for any device + { + Type: 'c', + Major: wildcard, + Minor: wildcard, + Permissions: "m", + Allow: true, + }, + { + Type: 'b', + Major: wildcard, + Minor: wildcard, + Permissions: "m", + Allow: true, + }, + { + Type: 'c', + Path: "/dev/null", + Major: 1, + Minor: 3, + Permissions: "rwm", + Allow: true, + }, + { + Type: 'c', + Path: "/dev/random", + Major: 1, + Minor: 8, + Permissions: "rwm", + Allow: true, + }, + { + Type: 'c', + Path: "/dev/full", + Major: 1, + Minor: 7, + Permissions: "rwm", + Allow: true, + }, + { + Type: 'c', + Path: "/dev/tty", + Major: 5, + Minor: 0, + Permissions: "rwm", + Allow: true, + }, + { + Type: 'c', + Path: "/dev/zero", + Major: 1, + Minor: 5, + Permissions: "rwm", + Allow: true, + }, + { + Type: 'c', + Path: "/dev/urandom", + Major: 1, + Minor: 9, + Permissions: "rwm", + Allow: true, + }, + { + Path: "/dev/console", + Type: 'c', + Major: 5, + Minor: 1, + Permissions: "rwm", + Allow: true, + }, + { + Path: "/dev/tty0", + Type: 'c', + Major: 4, + Minor: 0, + Permissions: "rwm", + Allow: true, + }, + { + Path: "/dev/tty1", + Type: 'c', + Major: 4, + Minor: 1, + Permissions: "rwm", + Allow: true, + }, + // /dev/pts/ - pts namespaces are "coming soon" + { + Path: "", + Type: 'c', + Major: 136, + Minor: wildcard, + Permissions: "rwm", + Allow: true, + }, + { + Path: "", + Type: 'c', + Major: 5, + Minor: 2, + Permissions: "rwm", + Allow: true, + }, + // tuntap + { + Path: "", + Type: 'c', + Major: 10, + Minor: 200, + Permissions: "rwm", + Allow: true, + }, +} + +var container libcontainer.Container + +func containerPreload(context *cli.Context) error { + c, err := getContainer(context) + if err != nil { + return err + } + container = c + return nil +} + +// loadFactory returns the configured factory instance for execing containers. +func loadFactory(context *cli.Context) (libcontainer.Factory, error) { + root := context.GlobalString("root") + abs, err := filepath.Abs(root) + if err != nil { + return nil, err + } + return libcontainer.New(abs, libcontainer.Cgroupfs, func(l *libcontainer.LinuxFactory) error { + l.CriuPath = context.GlobalString("criu") + return nil + }) +} + +// getContainer returns the specified container instance by loading it from state +// with the default factory. +func getContainer(context *cli.Context) (libcontainer.Container, error) { + id := context.Args().First() + if id == "" { + return nil, errEmptyID + } + factory, err := loadFactory(context) + if err != nil { + return nil, err + } + return factory.Load(id) +} + +// fatal prints the error's details if it is a libcontainer specific error type +// then exits the program with an exit status of 1. +func fatal(err error) { + if lerr, ok := err.(libcontainer.Error); ok { + lerr.Detail(os.Stderr) + os.Exit(1) + } + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} + +func getDefaultImagePath(context *cli.Context) string { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + return filepath.Join(cwd, "checkpoint") +} + +// newProcess returns a new libcontainer Process with the arguments from the +// spec and stdio from the current process. +func newProcess(p specs.Process) *libcontainer.Process { + return &libcontainer.Process{ + Args: p.Args, + Env: p.Env, + // TODO: fix libcontainer's API to better support uid/gid in a typesafe way. + User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID), + Cwd: p.Cwd, + } +} + +func dupStdio(process *libcontainer.Process, rootuid int) error { + process.Stdin = os.Stdin + process.Stdout = os.Stdout + process.Stderr = os.Stderr + for _, fd := range []uintptr{ + os.Stdin.Fd(), + os.Stdout.Fd(), + os.Stderr.Fd(), + } { + if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil { + return err + } + } + return nil +} + +// If systemd is supporting sd_notify protocol, this function will add support +// for sd_notify protocol from within the container. +func setupSdNotify(spec *specs.LinuxSpec, notifySocket string) { + spec.Mounts = append(spec.Mounts, specs.Mount{Destination: notifySocket, Type: "bind", Source: notifySocket, Options: []string{"bind"}}) + spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket)) +} + +func destroy(container libcontainer.Container) { + if err := container.Destroy(); err != nil { + logrus.Error(err) + } +} + +func setupIO(process *libcontainer.Process, rootuid int, console string, createTTY, detach bool) (*tty, error) { + // detach and createTty will not work unless a console path is passed + // so error out here before changing any terminal settings + if createTTY && detach && console == "" { + return nil, fmt.Errorf("cannot allocate tty if runc will detach") + } + if createTTY { + return createTty(process, rootuid, console) + } + if detach { + if err := dupStdio(process, rootuid); err != nil { + return nil, err + } + return nil, nil + } + return createStdioPipes(process, rootuid) +} + +func createPidFile(path string, process *libcontainer.Process) error { + pid, err := process.Pid() + if err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + _, err = fmt.Fprintf(f, "%d", pid) + return err +} + +func createContainer(context *cli.Context, id string, spec *specs.LinuxSpec) (libcontainer.Container, error) { + config, err := createLibcontainerConfig(id, spec) + if err != nil { + return nil, err + } + + if _, err := os.Stat(config.Rootfs); err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("rootfs (%q) does not exist", config.Rootfs) + } + return nil, err + } + + factory, err := loadFactory(context) + if err != nil { + return nil, err + } + return factory.Create(id, config) +} + +// runProcess will create a new process in the specified container +// by executing the process specified in the 'config'. +func runProcess(container libcontainer.Container, config *specs.Process, listenFDs []*os.File, console string, pidFile string, detach bool) (int, error) { + process := newProcess(*config) + + // Add extra file descriptors if needed + if len(listenFDs) > 0 { + process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(listenFDs)), "LISTEN_PID=1") + process.ExtraFiles = append(process.ExtraFiles, listenFDs...) + } + + rootuid, err := container.Config().HostUID() + if err != nil { + return -1, err + } + + tty, err := setupIO(process, rootuid, console, config.Terminal, detach) + if err != nil { + return -1, err + } + + if err := container.Start(process); err != nil { + tty.Close() + return -1, err + } + + if pidFile != "" { + if err := createPidFile(pidFile, process); err != nil { + process.Signal(syscall.SIGKILL) + process.Wait() + tty.Close() + return -1, err + } + } + if detach { + return 0, nil + } + handler := newSignalHandler(tty) + defer handler.Close() + + return handler.forward(process) +} diff --git a/vendor/src/github.com/opencontainers/specs/.travis.yml b/vendor/src/github.com/opencontainers/specs/.travis.yml index 64b57bf..5e1b19d 100644 --- a/vendor/src/github.com/opencontainers/specs/.travis.yml +++ b/vendor/src/github.com/opencontainers/specs/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.5.1 + - 1.5.3 - 1.4.3 - 1.3.3 diff --git a/vendor/src/github.com/opencontainers/specs/ChangeLog b/vendor/src/github.com/opencontainers/specs/ChangeLog new file mode 100644 index 0000000..865de0b --- /dev/null +++ b/vendor/src/github.com/opencontainers/specs/ChangeLog @@ -0,0 +1,217 @@ +OpenContainers Specifications + +Changes with v0.3.0: + Breaking changes: + + * config: Single, unified config file, #284 + * config: cwd is a required default, and must be absolute, #286, + #307, #308, #312 + * config: qualify the name of the version field, #309 + * config-linux: Convert classID from hex to uint32, #296 + * config-linux: Separate mknod from cgroups, #298 + + Additions: + + * config-linux: Add NoNewPrivileges setting for linux, #290 + + Minor fixes and documentation: + + * config-linux: clarify oom_score_adj, #236, #292 + * config-linux: Update links to cgroups documentation, #318 + * config-linux: Remove pointers for slices preferring omitempty + tag instead, #316 + * README: add runtime, bundle, and hook author user, #280 + * ROADMAP: reshuffled and split into GitHub issues, #300, #301, + #304, #306 + * style: Collect established styles in a discoverable location, #287, #311 + +Changes with v0.2.0: + * Add Apparmor, Selinux and Seccomp + * Add Apparmor, Selinux and Seccomp sections + * Add bind mount example + * Add fd section for linux container process + * Add Go types for specification + * *: adding a code of conduct + * Adding cgroups path to the Spec. + * .: Adding listing of implementations + * .: adding travis file for future CI + * Add license and DCO information for contributions + * Add linux spec description + * Add MAINTAINERS file + * Add memory swappiness to linux spec + * Add runtime state configuration and structs + * Adds a section for user namespace mappings + * Adds link to kernel cgroups documentation + * Adds section for Linux Rlimits + * Adds section for Linux Sysctl. + * Adds user namespace to the list of namespaces + * bundle: add initial run use case + * bundle: Fix 'and any number of and other related' typo + * bundle.md: clarify arbitrary/conventional dirnames + * bundle.md: fix link formatting + * bundle.md: fix off-by-one error + * bundle.md: various updates to latest spec + * bundle: Move 'Linux sysctl' header to its own line + * Change commiter to committer + * Change Device field order in spec_linux.go, 'Path' should be top of the 'Type' field, according to the different of the config-linux.md, 'Path' field is the unique key. + * Change layout of mountpoints and mounts + * Change the rlimit type to string instead of int + * Clarify behavior around namespaces paths. + * config: Add example additionalGids + * config: Add example cwd + * config: cleanup language on readonly parameter + * config: fix links to go files + * config-linux: specify the default devices/filesystems available + * config.md: clarify destination for mounts + * config.md: make the version a semver + * config.md: make the version field example a semver + * config.md: minor clean up of process specification + * config.md: reformat into a standard style + * config.md: update links to spec schema code + * config.md: various cleanup/consistency fixes + * config: minor cleanup + * Deduplicate the field of RootfsPropagation + * Define constants for Linux Namespace names + * Fix LinuxRuntime field + * Fix root object keys + * Fix typos in config.md + * Fix typos in the "Namespace types" section + * Fix typos in the rlimits section + * Fix Windows path escaping in example mount JSON + * JSON objects are easier to parse/manipulate + * made repo public. Added warning in README + * Make namespaces match runc + * make rootfs mount propagation mode settable + * Makes namespaces description linux specific + * *.md: markdown formatting + * Modify the capabilities constants to match header files like other constants + * Move linux specific options to linux spec + * README: add a rule for paragraph formatting in markdown + * README: Document BlueJeans and wiki archive for meetings + * README: Document pre-meeting agenda alteration + * README: Document YouTube and IRC backchannel for meetings + * README: Focus on local runtime (create/start/stop) + * README.md: Add a git commit style guide + * README.md: contribution about discussion + * README: releases section + * README: Remove blank line from infrastructure-agnostic paragraph + * removed boilerplate file + * *: remove superfluous comma in code-of-conduct + * Remove trailing whitespace + * Rename SystemProperties to Sysctl + * Rename the header "Access to devices" to "Devices" to fit with the config + * *: re-org the spec + * Replace Linux.Device with more specific config + * restore formatting + * Return golang compliant names for UID and GID in User + * Return golint-compliant naming for mappings + * runtime: Add prestart/poststop hooks + * runtime_config: comments for golint + * runtime-config-linux: Drop 'Linux' from headers + * runtime_config_linux: Fix 'LinuxSpec' -> 'LinuxRuntimeSpec' in comment + * runtime-config-linux: One sentence per line for opening two paragraphs + * runtime-config: Remove blank lines from the end of files + * runtime-config: Remove 'destination' docs from mounts + * runtime.md: convert oc to runc + * runtime: use opencontainer vs oci + * *: small spelling fixes + * Specific platform specific user struct for spec + * spec: linux: add support for the PIDs cgroup + * spec_linux: conform to `golint` + * spec_linux.go: Rename IDMapping fields to follow syscall.SysProcIDMap + * spec_linux: remove ending periods on one-line comments + * spec: rename ocp to oci and add a link + * specs: add json notation + * specs: align the ascii graph + * specs: fix the description for the [ug]idMappings + * specs: introduce the concept of a runtime.json + * .tools: cleanup the commit entry + * .tools: repo validation tool + * travis: fix DCO validation for merges + * typo: containers -> container's + * typo: the -> for + * Update config-linux for better formatting on values + * Update README.md + * Update readme with weekly call and mailing list + * Update runtime.md + * Update runtime.md + * Update runtime.md + * version: more explicit version for comparison + +Changes with v0.1.0: + * Add Architecture field to Seccomp configuration in Linux runtime + * Add @hqhq as maintainer + * Add hyphen for host specific + * Adding Vishnu Kannan as a Maintainer. + * Add initial roadmap + * Add lifecycle for containers + * Add oom_score_adj to the runtime Spec. + * Add post-start hooks + * Add Seccomp constants to description of Linux runtime spec + * Add Seccomp constants to Linux runtime config + * Add some clarity around the state.json file + * adds text describing the upper-case keywords used in the spec + * add testing framework to ROADMAP + * Appropriately mark optional fields as omitempty + * cgroup: Add support for memory.kmem.tcp.limit_in_bytes + * Change HugepageLimit.Limit type to uint64 + * Change the behavior when cgroupsPath is absent + * Change version from 0.1.0 to 0.2.0 + * Clarify the semantics of hook elements + * Cleanup bundle.md + * Cleanup principles + * config: linux: update description of PidsLimit + * config: Require a new UTS namespace for config.json's hostname + * config: Require the runtime to mount Spec.Mounts in order + * convert **name** to **`name`** + * Example lists "root' but text mentions "bundlePath" + * Fix an extra space in VersionMinor + * Fix golint warnings + * Fix typo in BlockIO struct comment + * Fix typo in Filesystem Bundle + * Fix value of swappiness + * glossary: Provide a quick overview of important terms + * glossary: Specify UTF-8 for all our JSON + * hooks: deduplicate the hooks docs + * implementations: Link to kunalkushwaha/octool + * implementations: Link to mrunalp/ocitools + * lifecycle: Don't require /run/opencontainer//containers + * lifecycle: Mention runtime.json + * lifecycle: no hypens + * MAINTAINERS: add tianon per the charter + * MAINTAINERS: correct Vish's github account + * Makefile: Add glossary to DOC_FILES + * Make optional Cgroup related config params pointers along with `omitempty` json tag. + * Mark RootfsPropagation as omitempty + * *.md: update TOC and links + * move the description of Rlimits before example + * move the description of user ns mapping to proper file + * principles: Give principles their own home + * *: printable documents + * Project: document release process + * README: Fix some headers + * README: make header more concise + * remove blank char from blank line + * Remove the unneeded build tag from the config_linux.go + * Remove trailing comma in hooks json example + * Rename State's Root to Bundle + * ROADMAP.md: remove the tail spaces + * roadmap: update links and add wiki reference + * runtime: Add 'version' to the state.json example + * runtime-config: add example label before json exmaple + * runtime-config: add section about Hooks + * runtime: config: linux: add cgroups information + * runtime: config: linux: Edit BlockIO struct + * runtime: config: linux: Fix typo and trailing commas in json example + * runtime_config_linux.go: add missing pointer + * runtime-config-linux.md: fix the type of cpus and mems + * runtime.md: fix spacing + * Talk about host specific/independent instead of mutability + * .tools: commit validator is a separate project + * .tools: make GetFetchHeadCommit do what it says + * .travis.yml: add go 1.5.1, update from 1.4.2 to 1.4.3 + * Update readme with wiki link to minutes + * Update Typo in ROADMAP.md + * Use unsigned for IDs + * version: introduce a string for dev indication + diff --git a/vendor/src/github.com/opencontainers/specs/Makefile b/vendor/src/github.com/opencontainers/specs/Makefile index d564e1a..224f085 100644 --- a/vendor/src/github.com/opencontainers/specs/Makefile +++ b/vendor/src/github.com/opencontainers/specs/Makefile @@ -4,6 +4,7 @@ DOC_FILES := \ README.md \ code-of-conduct.md \ principles.md \ + style.md \ ROADMAP.md \ implementations.md \ bundle.md \ @@ -11,11 +12,11 @@ DOC_FILES := \ runtime-linux.md \ config.md \ config-linux.md \ - runtime-config.md \ - runtime-config-linux.md \ glossary.md +EPOCH_TEST_COMMIT := 041eb73d2e0391463894c04c8ac938036143eba3 docs: pdf html +.PHONY: docs pdf: @mkdir -p output/ && \ @@ -39,6 +40,22 @@ html: vbatts/pandoc -f markdown_github -t html5 -o /output/docs.html $(patsubst %,/input/%,$(DOC_FILES)) && \ ls -sh $(shell readlink -f output/docs.html) +.PHONY: test .govet .golint .gitvalidation + +test: .govet .golint .gitvalidation + +# `go get golang.org/x/tools/cmd/vet` +.govet: + go vet -x ./... + +# `go get github.com/golang/lint/golint` +.golint: + golint ./... + +# `go get github.com/vbatts/git-validation` +.gitvalidation: + git-validation -q -run DCO,short-subject -v -range $(EPOCH_TEST_COMMIT)..HEAD + clean: rm -rf output/ *~ diff --git a/vendor/src/github.com/opencontainers/specs/README.md b/vendor/src/github.com/opencontainers/specs/README.md index e838b01..bfff859 100644 --- a/vendor/src/github.com/opencontainers/specs/README.md +++ b/vendor/src/github.com/opencontainers/specs/README.md @@ -6,12 +6,11 @@ Table of Contents - [Container Principles](principles.md) +- [Specification Style](style.md) - [Filesystem Bundle](bundle.md) - Configuration - - [Container Configuration](config.md) - - [Container Configuration (Linux-specific)](config-linux.md) - - [Runtime Configuration](runtime-config.md) - - [Runtime Configuration (Linux-specific)](runtime-config-linux.md) + - [General](config.md) + - [Linux-specific](config-linux.md) - [Runtime and Lifecycle](runtime.md) - [Linux Specific Runtime](runtime-linux.md) - [Implementations](implementations.md) @@ -23,9 +22,20 @@ In the specifications in the above table of contents, the keywords "MUST", "MUST To provide context for users the following section gives example use cases for each part of the spec. -## Filesystem Bundle & Configuration +#### Application Bundle Builders -- A user can create a root filesystem and configuration, with low-level OS and host specific details, and launch it as a container under an Open Container runtime. +Application bundle builders can create a [bundle](bundle.md) directory that includes all of the files required for launching an application as a container. +The bundle contains an OCI [configuration file](config.md) where the builder can specify host-independent details such as [which executable to launch](config.md#process-configuration) and host-specific settings such as [mount](config.md#mounts) locations, [hook](config.md#hooks) paths, Linux [namespaces](config-linux.md#namespaces) and [cgroups](config-linux.md#control-groups). +Because the configuration includes host-specific settings, application bundle directories copied between two hosts may require configuration adjustments. + +#### Hook Developers + +[Hook](config.md#hooks) developers can extend the functionality of an OCI-compliant runtime by hooking into a container's lifecycle with an external application. +Example use cases include sophisticated network configuration, volume garbage collection, etc. + +#### Runtime Developers + +Runtime developers can build runtime implementations that run OCI-compliant bundles and container configuration, containing low-level OS and host specific details, on a particular platform. # Releases diff --git a/vendor/src/github.com/opencontainers/specs/ROADMAP.md b/vendor/src/github.com/opencontainers/specs/ROADMAP.md index 37e3e9f..67620dd 100644 --- a/vendor/src/github.com/opencontainers/specs/ROADMAP.md +++ b/vendor/src/github.com/opencontainers/specs/ROADMAP.md @@ -17,13 +17,6 @@ Although OCI doesn't define a transport method we should have a cryptographic di *Owner:* philips -### Review the need for runtime.json - -There are some discussions about having `runtime.json` being optional for containers and specifying defaults. -Runtimes would use this standard set of defaults for containers and `runtime.json` would provide overrides for fine tuning of these extra host or platform specific settings. - -*Owner:* - ### Define Container Lifecycle Containers have a lifecycle and being able to identify and document the lifecycle of a container is very helpful for implementations of the spec. @@ -31,38 +24,29 @@ The lifecycle events of a container also help identify areas to implement hooks *Owner:* mrunalp -### Define Standard Container Actions +### Define Standard Container Actions (Target release: v0.3.0) Define what type of actions a runtime can perform on a container without imposing hardships on authors of platforms that do not support advanced options. -*Owner:* - -### Clarify rootfs requirement in base spec - -Is the rootfs needed or should it just be expected in the bundle without having a field in the spec? - -*Owner:* +*Owner:* duglin ### Container Definition Define what a software container is and its attributes in a cross platform way. -*Owner:* +Could be solved by lifecycle/ops and create/start split discussions + +*Owner:* vishh & duglin ### Live Container Updates Should we allow dynamic container updates to runtime options? -*Owner:* vishh +Proposal: make it an optional feature -### Protobuf Config +*Owner:* hqhq (was vishh) robdolinms, bcorrie -We currently have only one language binding for the spec and that is Go. -If we change the specs format in the respository to be something like protobuf then the generation for multiple language bindings become effortless. - -*Owner:* vbatts - -### Validation Tooling +### Validation Tooling (Target release: v0.3.0) Provide validation tooling for compliance with OCI spec and runtime environment. @@ -78,12 +62,18 @@ Provide a testing framework for compliance with OCI spec and runtime environment Decide on a robust versioning schema for the spec as it evolves. -*Owner:* +Resolved but release process could evolve. Resolved for v0.2.0, expect to revisit near v1.0.0 + +*Owner:* vbatts ### Printable/Compiled Spec Regardless of how the spec is written, ensure that it is easy to read and follow for first time users. +Part of this is resolved. Produces an html & pdf. +Done +Would be nice to publish to the OCI web site as part of our release process. + *Owner:* vbatts ### Base Config Compatibility @@ -96,9 +86,18 @@ Systems: * Windows * Linux -*Owner:* +*Owner:* robdolinms as lead coordinator ### Full Lifecycle Hooks + Ensure that we have lifecycle hooks in the correct places with full coverage over the container lifecycle. +Will probably go away with Vish's work on splitting create and start, and if we have exec. + *Owner:* + +### Distributable Format + +A common format for serializing and distributing bundles. + +*Owner:* vbatts diff --git a/vendor/src/github.com/opencontainers/specs/bundle.md b/vendor/src/github.com/opencontainers/specs/bundle.md index 6c1045c..5b1925b 100644 --- a/vendor/src/github.com/opencontainers/specs/bundle.md +++ b/vendor/src/github.com/opencontainers/specs/bundle.md @@ -8,23 +8,17 @@ See also [OS X application bundles](http://en.wikipedia.org/wiki/Bundle_%28OS_X% The definition of a bundle is only concerned with how a container, and its configuration data, are stored on a local file system so that it can be consumed by a compliant runtime. A Standard Container bundle contains all the information needed to load and run a container. -This includes the following three artifacts which MUST all reside in the same directory on the local filesystem: +This includes the following artifacts which MUST all reside in the same directory on the local filesystem: -1. `config.json` : contains host-independent configuration data. -This REQUIRED file, which MUST be named `config.json`, contains settings that are host-independent and application-specific such as security permissions, environment variables and arguments. +1. `config.json` : contains configuration data. +This REQUIRED file, which MUST be named `config.json`. When the bundle is packaged up for distribution, this file MUST be included. See [`config.json`](config.md) for more details. -2. `runtime.json` : contains host-specific configuration data. -This REQUIRED file, which MUST be named `runtime.json`, contains settings that are host-specific such as mount sources and hooks. -The goal is that the bundle can be moved as a unit to another runtime and run the same application once a host-specific `runtime.json` is defined. -When the bundle is packaged up for distribution, this file MUST NOT be included. -See [`runtime.json`](runtime-config.md) for more details. - -3. A directory representing the root filesystem of the container. +2. A directory representing the root filesystem of the container. While the name of this REQUIRED directory may be arbitrary, users should consider using a conventional name, such as `rootfs`. When the bundle is packaged up for distribution, this directory MUST be included. This directory MUST be referenced from within the `config.json` file. -While these three artifacts MUST all be present in a single directory on the local filesystem, that directory itself is not part of the bundle. +While these artifacts MUST all be present in a single directory on the local filesystem, that directory itself is not part of the bundle. In other words, a tar archive of a *bundle* will have these artifacts at the root of the archive, not nested within a top-level directory. diff --git a/vendor/src/github.com/opencontainers/specs/config-linux.md b/vendor/src/github.com/opencontainers/specs/config-linux.md index 7049e95..f7723c7 100644 --- a/vendor/src/github.com/opencontainers/specs/config-linux.md +++ b/vendor/src/github.com/opencontainers/specs/config-linux.md @@ -16,24 +16,594 @@ Valid values are the strings for capabilities defined in [the man page](http://m ] ``` -## Default Devices and File Systems +## Default File Systems The Linux ABI includes both syscalls and several special file paths. Applications expecting a Linux environment will very likely expect these files paths to be setup correctly. -The following devices and filesystems MUST be made available in each application's filesystem +The following filesystems MUST be made available in each application's filesystem -| Path | Type | Notes | -| ------------ | ------ | ------- | -| /proc | [procfs](https://www.kernel.org/doc/Documentation/filesystems/proc.txt) | | -| /sys | [sysfs](https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt) | | -| /dev/null | [device](http://man7.org/linux/man-pages/man4/null.4.html) | | -| /dev/zero | [device](http://man7.org/linux/man-pages/man4/zero.4.html) | | -| /dev/full | [device](http://man7.org/linux/man-pages/man4/full.4.html) | | -| /dev/random | [device](http://man7.org/linux/man-pages/man4/random.4.html) | | -| /dev/urandom | [device](http://man7.org/linux/man-pages/man4/random.4.html) | | -| /dev/tty | [device](http://man7.org/linux/man-pages/man4/tty.4.html) | | -| /dev/console | [device](http://man7.org/linux/man-pages/man4/console.4.html) | | -| /dev/pts | [devpts](https://www.kernel.org/doc/Documentation/filesystems/devpts.txt) | | -| /dev/ptmx | [device](https://www.kernel.org/doc/Documentation/filesystems/devpts.txt) | Bind-mount or symlink of /dev/pts/ptmx | -| /dev/shm | [tmpfs](https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt) | | +| Path | Type | +| -------- | ------ | +| /proc | [procfs](https://www.kernel.org/doc/Documentation/filesystems/proc.txt) | +| /sys | [sysfs](https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt) | +| /dev/pts | [devpts](https://www.kernel.org/doc/Documentation/filesystems/devpts.txt) | +| /dev/shm | [tmpfs](https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt) | + +## Namespaces + +A namespace wraps a global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource. +Changes to the global resource are visible to other processes that are members of the namespace, but are invisible to other processes. +For more information, see [the man page](http://man7.org/linux/man-pages/man7/namespaces.7.html). + +Namespaces are specified as an array of entries inside the `namespaces` root field. +The following parameters can be specified to setup namespaces: + +* **`type`** *(string, required)* - namespace type. The following namespaces types are supported: + * **`pid`** processes inside the container will only be able to see other processes inside the same container + * **`network`** the container will have its own network stack + * **`mount`** the container will have an isolated mount table + * **`ipc`** processes inside the container will only be able to communicate to other processes inside the same container via system level IPC + * **`uts`** the container will be able to have its own hostname and domain name + * **`user`** the container will be able to remap user and group IDs from the host to local users and groups within the container + +* **`path`** *(string, optional)* - path to namespace file + +If a path is specified, that particular file is used to join that type of namespace. +Also, when a path is specified, a runtime MUST assume that the setup for that particular namespace has already been done and error out if the config specifies anything else related to that namespace. + +###### Example + +```json + "namespaces": [ + { + "type": "pid", + "path": "/proc/1234/ns/pid" + }, + { + "type": "network", + "path": "/var/run/netns/neta" + }, + { + "type": "mount" + }, + { + "type": "ipc" + }, + { + "type": "uts" + }, + { + "type": "user" + } + ] +``` + +## User namespace mappings + +###### Example + +```json + "uidMappings": [ + { + "hostID": 1000, + "containerID": 0, + "size": 10 + } + ], + "gidMappings": [ + { + "hostID": 1000, + "containerID": 0, + "size": 10 + } + ] +``` + +uid/gid mappings describe the user namespace mappings from the host to the container. +The mappings represent how the bundle `rootfs` expects the user namespace to be setup and the runtime SHOULD NOT modify the permissions on the rootfs to realize the mapping. +*hostID* is the starting uid/gid on the host to be mapped to *containerID* which is the starting uid/gid in the container and *size* refers to the number of ids to be mapped. +There is a limit of 5 mappings which is the Linux kernel hard limit. + +## Devices + +`devices` is an array specifying the list of devices that MUST be available in the container. +The runtime may supply them however it likes (with [mknod][mknod.2], by bind mounting from the runtime mount namespace, etc.). + +The following parameters can be specified: + +* **`type`** *(char, required)* - type of device: `c`, `b`, `u` or `p`. + More info in [mknod(1)][mknod.1]. +* **`path`** *(string, required)* - full path to device inside container. +* **`major, minor`** *(int64, required unless **`type`** is `p`)* - [major, minor numbers][devices] for the device. +* **`fileMode`** *(uint32, optional)* - file mode for the device. + You can also control access to devices [with cgroups](#device-whitelist). +* **`uid`** *(uint32, optional)* - id of device owner. +* **`gid`** *(uint32, optional)* - id of device group. + +###### Example + +```json + "devices": [ + { + "path": "/dev/fuse", + "type": "c", + "major": 10, + "minor": 229, + "fileMode": 0666, + "uid": 0, + "gid": 0 + }, + { + "path": "/dev/sda", + "type": "b", + "major": 8, + "minor": 0, + "fileMode": 0660, + "uid": 0, + "gid": 0 + } + ] +``` + +###### Default Devices + +In addition to any devices configured with this setting, the runtime MUST also supply: + +* [`/dev/null`][null.4] +* [`/dev/zero`][zero.4] +* [`/dev/full`][full.4] +* [`/dev/random`][random.4] +* [`/dev/urandom`][random.4] +* [`/dev/tty`][tty.4] +* [`/dev/console`][console.4] +* [`/dev/ptmx`][pts.4]. + A [bind-mount or symlink of the container's `/dev/pts/ptmx`][devpts]. + +## Control groups + +Also known as cgroups, they are used to restrict resource usage for a container and handle device access. +cgroups provide controls to restrict cpu, memory, IO, pids and network for the container. +For more information, see the [kernel cgroups documentation][cgroup-v1]. + +The path to the cgroups can be specified in the Spec via `cgroupsPath`. +`cgroupsPath` is expected to be relative to the cgroups mount point. +If `cgroupsPath` is not specified, implementations can define the default cgroup path. +Implementations of the Spec can choose to name cgroups in any manner. +The Spec does not include naming schema for cgroups. +The Spec does not support [split hierarchy][cgroup-v2]. +The cgroups will be created if they don't exist. + +###### Example + +```json + "cgroupsPath": "/myRuntime/myContainer" +``` + +`cgroupsPath` can be used to either control the cgroups hierarchy for containers or to run a new process in an existing container. + +You can configure a container's cgroups via the `resources` field of the Linux configuration. +Do not specify `resources` unless limits have to be updated. +For example, to run a new process in an existing container without updating limits, `resources` need not be specified. + +#### Device whitelist + +`devices` is an array of entries to control the [device whitelist][cgroup-v1-devices]. +The runtime MUST apply entries in the listed order. + +The following parameters can be specified: + +* **`allow`** *(boolean, required)* - whether the entry is allowed or denied. +* **`type`** *(char, optional)* - type of device: `a` (all), `c` (char), or `b` (block). + `null` or unset values mean "all", mapping to `a`. +* **`major, minor`** *(int64, optional)* - [major, minor numbers][devices] for the device. + `null` or unset values mean "all", mapping to [`*` in the filesystem API][cgroup-v1-devices]. +* **`access`** *(string, optional)* - cgroup permissions for device. + A composition of `r` (read), `w` (write), and `m` (mknod). + +###### Example + +```json + "devices": [ + { + "allow": false, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 10, + "minor": 229, + "access": "rw" + }, + { + "allow": true, + "type": "b", + "major": 8, + "minor": 0, + "access": "r" + } + ] +``` + +#### Disable out-of-memory killer + +`disableOOMKiller` contains a boolean (`true` or `false`) that enables or disables the Out of Memory killer for a cgroup. +If enabled (`false`), tasks that attempt to consume more memory than they are allowed are immediately killed by the OOM killer. +The OOM killer is enabled by default in every cgroup using the `memory` subsystem. +To disable it, specify a value of `true`. +For more information, see [the memory cgroup man page][cgroup-v1-memory]. + +* **`disableOOMKiller`** *(bool, optional)* - enables or disables the OOM killer + +###### Example + +```json + "disableOOMKiller": false +``` + +#### Set oom_score_adj + +`oomScoreAdj` sets heuristic regarding how the process is evaluated by the kernel during memory pressure. +For more information, see [the proc filesystem documentation section 3.1](https://www.kernel.org/doc/Documentation/filesystems/proc.txt). +This is a kernel/system level setting, where as `disableOOMKiller` is scoped for a memory cgroup. +For more information on how these two settings work together, see [the memory cgroup documentation section 10. OOM Contol][cgroup-v1-memory]. + +* **`oomScoreAdj`** *(int, optional)* - adjust the oom-killer score + +###### Example + +```json + "oomScoreAdj": 0 +``` + +#### Memory + +`memory` represents the cgroup subsystem `memory` and it's used to set limits on the container's memory usage. +For more information, see [the memory cgroup man page][cgroup-v1-memory]. + +The following parameters can be specified to setup the controller: + +* **`limit`** *(uint64, optional)* - sets limit of memory usage + +* **`reservation`** *(uint64, optional)* - sets soft limit of memory usage + +* **`swap`** *(uint64, optional)* - sets limit of memory+Swap usage + +* **`kernel`** *(uint64, optional)* - sets hard limit for kernel memory + +* **`kernelTCP`** *(uint64, optional)* - sets hard limit for kernel memory in tcp using + +* **`swappiness`** *(uint64, optional)* - sets swappiness parameter of vmscan (See sysctl's vm.swappiness) + +###### Example + +```json + "memory": { + "limit": 0, + "reservation": 0, + "swap": 0, + "kernel": 0, + "kernelTCP": 0, + "swappiness": 0 + } +``` + +#### CPU + +`cpu` represents the cgroup subsystems `cpu` and `cpusets`. +For more information, see [the cpusets cgroup man page][cgroup-v1-cpusets]. + +The following parameters can be specified to setup the controller: + +* **`shares`** *(uint64, optional)* - specifies a relative share of CPU time available to the tasks in a cgroup + +* **`quota`** *(uint64, optional)* - specifies the total amount of time in microseconds for which all tasks in a cgroup can run during one period (as defined by **`period`** below) + +* **`period`** *(uint64, optional)* - specifies a period of time in microseconds for how regularly a cgroup's access to CPU resources should be reallocated (CFS scheduler only) + +* **`realtimeRuntime`** *(uint64, optional)* - specifies a period of time in microseconds for the longest continuous period in which the tasks in a cgroup have access to CPU resources + +* **`realtimePeriod`** *(uint64, optional)* - same as **`period`** but applies to realtime scheduler only + +* **`cpus`** *(string, optional)* - list of CPUs the container will run in + +* **`mems`** *(string, optional)* - list of Memory Nodes the container will run in + +###### Example + +```json + "cpu": { + "shares": 0, + "quota": 0, + "period": 0, + "realtimeRuntime": 0, + "realtimePeriod": 0, + "cpus": "", + "mems": "" + } +``` + +#### Block IO Controller + +`blockIO` represents the cgroup subsystem `blkio` which implements the block io controller. +For more information, see [the kernel cgroups documentation about blkio][cgroup-v1-blkio]. + +The following parameters can be specified to setup the controller: + +* **`blkioWeight`** *(uint16, optional)* - specifies per-cgroup weight. This is default weight of the group on all devices until and unless overridden by per-device rules. The range is from 10 to 1000. + +* **`blkioLeafWeight`** *(uint16, optional)* - equivalents of `blkioWeight` for the purpose of deciding how much weight tasks in the given cgroup has while competing with the cgroup's child cgroups. The range is from 10 to 1000. + +* **`blkioWeightDevice`** *(array, optional)* - specifies the list of devices which will be bandwidth rate limited. The following parameters can be specified per-device: + * **`major, minor`** *(int64, required)* - major, minor numbers for device. More info in `man mknod`. + * **`weight`** *(uint16, optional)* - bandwidth rate for the device, range is from 10 to 1000 + * **`leafWeight`** *(uint16, optional)* - bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + + You must specify at least one of `weight` or `leafWeight` in a given entry, and can specify both. + +* **`blkioThrottleReadBpsDevice`**, **`blkioThrottleWriteBpsDevice`**, **`blkioThrottleReadIOPSDevice`**, **`blkioThrottleWriteIOPSDevice`** *(array, optional)* - specify the list of devices which will be IO rate limited. The following parameters can be specified per-device: + * **`major, minor`** *(int64, required)* - major, minor numbers for device. More info in `man mknod`. + * **`rate`** *(uint64, required)* - IO rate limit for the device + +###### Example + +```json + "blockIO": { + "blkioWeight": 0, + "blkioLeafWeight": 0, + "blkioWeightDevice": [ + { + "major": 8, + "minor": 0, + "weight": 500, + "leafWeight": 300 + }, + { + "major": 8, + "minor": 16, + "weight": 500 + } + ], + "blkioThrottleReadBpsDevice": [ + { + "major": 8, + "minor": 0, + "rate": 600 + } + ], + "blkioThrottleWriteIOPSDevice": [ + { + "major": 8, + "minor": 16, + "rate": 300 + } + ] + } +``` + +#### Huge page limits + +`hugepageLimits` represents the `hugetlb` controller which allows to limit the +HugeTLB usage per control group and enforces the controller limit during page fault. +For more information, see the [kernel cgroups documentation about HugeTLB][cgroup-v1-hugetlb]. + +`hugepageLimits` is an array of entries, each having the following structure: + +* **`pageSize`** *(string, required)* - hugepage size + +* **`limit`** *(uint64, required)* - limit in bytes of *hugepagesize* HugeTLB usage + +###### Example + +```json + "hugepageLimits": [ + { + "pageSize": "2MB", + "limit": 9223372036854771712 + } + ] +``` + +#### Network + +`network` represents the cgroup subsystems `net_cls` and `net_prio`. +For more information, see [the net\_cls cgroup man page][cgroup-v1-net-cls] and [the net\_prio cgroup man page][cgroup-v1-net-prio]. + +The following parameters can be specified to setup these cgroup controllers: + +* **`classID`** *(uint32, optional)* - is the network class identifier the cgroup's network packets will be tagged with + +* **`priorities`** *(array, optional)* - specifies a list of objects of the priorities assigned to traffic originating from +processes in the group and egressing the system on various interfaces. The following parameters can be specified per-priority: + * **`name`** *(string, required)* - interface name + * **`priority`** *(uint32, required)* - priority applied to the interface + +###### Example + +```json + "network": { + "classID": 1048577, + "priorities": [ + { + "name": "eth0", + "priority": 500 + }, + { + "name": "eth1", + "priority": 1000 + } + ] + } +``` + +#### PIDs + +`pids` represents the cgroup subsystem `pids`. +For more information, see [the pids cgroup man page][cgroup-v1-pids]. + +The following paramters can be specified to setup the controller: + +* **`limit`** *(int64, required)* - specifies the maximum number of tasks in the cgroup + +###### Example + +```json + "pids": { + "limit": 32771 + } +``` + +## Sysctl + +sysctl allows kernel parameters to be modified at runtime for the container. +For more information, see [the man page](http://man7.org/linux/man-pages/man8/sysctl.8.html) + +###### Example + +```json + "sysctl": { + "net.ipv4.ip_forward": "1", + "net.core.somaxconn": "256" + } +``` + +## Rlimits + +rlimits allow setting resource limits. +`type` is a string with a value from those defined in [the man page](http://man7.org/linux/man-pages/man2/setrlimit.2.html). +The kernel enforces the `soft` limit for a resource while the `hard` limit acts as a ceiling for that value that could be set by an unprivileged process. + +###### Example + +```json + "rlimits": [ + { + "type": "RLIMIT_NPROC", + "soft": 1024, + "hard": 102400 + } + ] +``` + +## SELinux process label + +SELinux process label specifies the label with which the processes in a container are run. +For more information about SELinux, see [Selinux documentation](http://selinuxproject.org/page/Main_Page) + +###### Example + +```json + "selinuxProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c124,c675" +``` + +## Apparmor profile + +Apparmor profile specifies the name of the apparmor profile that will be used for the container. +For more information about Apparmor, see [Apparmor documentation](https://wiki.ubuntu.com/AppArmor) + +###### Example + +```json + "apparmorProfile": "acme_secure_profile" +``` + +## seccomp + +Seccomp provides application sandboxing mechanism in the Linux kernel. +Seccomp configuration allows one to configure actions to take for matched syscalls and furthermore also allows matching on values passed as arguments to syscalls. +For more information about Seccomp, see [Seccomp kernel documentation](https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt) +The actions, architectures, and operators are strings that match the definitions in seccomp.h from [libseccomp](https://github.com/seccomp/libseccomp) and are translated to corresponding values. +A valid list of constants as of Libseccomp v2.2.3 is contained below. + +Architecture Constants +* `SCMP_ARCH_X86` +* `SCMP_ARCH_X86_64` +* `SCMP_ARCH_X32` +* `SCMP_ARCH_ARM` +* `SCMP_ARCH_AARCH64` +* `SCMP_ARCH_MIPS` +* `SCMP_ARCH_MIPS64` +* `SCMP_ARCH_MIPS64N32` +* `SCMP_ARCH_MIPSEL` +* `SCMP_ARCH_MIPSEL64` +* `SCMP_ARCH_MIPSEL64N32` + +Action Constants: +* `SCMP_ACT_KILL` +* `SCMP_ACT_TRAP` +* `SCMP_ACT_ERRNO` +* `SCMP_ACT_TRACE` +* `SCMP_ACT_ALLOW` + +Operator Constants: +* `SCMP_CMP_NE` +* `SCMP_CMP_LT` +* `SCMP_CMP_LE` +* `SCMP_CMP_EQ` +* `SCMP_CMP_GE` +* `SCMP_CMP_GT` +* `SCMP_CMP_MASKED_EQ` + +###### Example + +```json + "seccomp": { + "defaultAction": "SCMP_ACT_ALLOW", + "architectures": [ + "SCMP_ARCH_X86" + ], + "syscalls": [ + { + "name": "getcwd", + "action": "SCMP_ACT_ERRNO" + } + ] + } +``` + +## Rootfs Mount Propagation + +rootfsPropagation sets the rootfs's mount propagation. +Its value is either slave, private, or shared. +[The kernel doc](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt) has more information about mount propagation. + +###### Example + +```json + "rootfsPropagation": "slave", +``` + +## No new privileges + +Setting `noNewPrivileges` to true prevents the processes in the container from gaining additional privileges. +[The kernel doc](https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt) has more information on how this is achieved using a prctl system call. + +###### Example + +```json + "noNewPrivileges": true, +``` + +[cgroup-v1]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt +[cgroup-v1-blkio]: https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt +[cgroup-v1-cpusets]: https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt +[cgroup-v1-devices]: https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt +[cgroup-v1-hugetlb]: https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt +[cgroup-v1-memory]: https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt +[cgroup-v1-net-cls]: https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt +[cgroup-v1-net-prio]: https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt +[cgroup-v1-pids]: https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt +[cgroup-v2]: https://www.kernel.org/doc/Documentation/cgroup-v2.txt +[devices]: https://www.kernel.org/doc/Documentation/devices.txt +[devpts]: https://www.kernel.org/doc/Documentation/filesystems/devpts.txt + +[mknod.1]: http://man7.org/linux/man-pages/man1/mknod.1.html +[mknod.2]: http://man7.org/linux/man-pages/man2/mknod.2.html +[console.4]: http://man7.org/linux/man-pages/man4/console.4.html +[full.4]: http://man7.org/linux/man-pages/man4/full.4.html +[null.4]: http://man7.org/linux/man-pages/man4/null.4.html +[pts.4]: http://man7.org/linux/man-pages/man4/pts.4.html +[random.4]: http://man7.org/linux/man-pages/man4/random.4.html +[tty.4]: http://man7.org/linux/man-pages/man4/tty.4.html +[zero.4]: http://man7.org/linux/man-pages/man4/zero.4.html diff --git a/vendor/src/github.com/opencontainers/specs/config.go b/vendor/src/github.com/opencontainers/specs/config.go index cff5336..7861c56 100644 --- a/vendor/src/github.com/opencontainers/specs/config.go +++ b/vendor/src/github.com/opencontainers/specs/config.go @@ -5,7 +5,7 @@ package specs // bundle is packaged for distribution. type Spec struct { // Version is the version of the specification that is supported. - Version string `json:"version"` + Version string `json:"ociVersion"` // Platform is the host information for OS and Arch. Platform Platform `json:"platform"` // Process is the container's main process. @@ -15,7 +15,9 @@ type Spec struct { // Hostname is the container's host name. Hostname string `json:"hostname,omitempty"` // Mounts profile configuration for adding mounts to the container's filesystem. - Mounts []MountPoint `json:"mounts"` + Mounts []Mount `json:"mounts"` + // Hooks are the commands run at various lifecycle events of the container. + Hooks Hooks `json:"hooks"` } // Process contains information to start a specific application inside the container. @@ -30,7 +32,7 @@ type Process struct { Env []string `json:"env,omitempty"` // Cwd is the current working directory for the process and must be // relative to the container's root. - Cwd string `json:"cwd,omitempty"` + Cwd string `json:"cwd"` } // Root contains information about the container's root filesystem on the host. @@ -50,10 +52,33 @@ type Platform struct { Arch string `json:"arch"` } -// MountPoint describes a directory that may be fullfilled by a mount in the runtime.json. -type MountPoint struct { - // Name is a unique descriptive identifier for this mount point. - Name string `json:"name"` - // Path specifies the path of the mount. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point. - Path string `json:"path"` +// Mount specifies a mount for a container. +type Mount struct { + // Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point. + Destination string `json:"destination"` + // Type specifies the mount kind. + Type string `json:"type"` + // Source specifies the source path of the mount. In the case of bind mounts on + // linux based systems this would be the file on the host. + Source string `json:"source"` + // Options are fstab style mount options. + Options []string `json:"options,omitempty"` +} + +// Hook specifies a command that is run at a particular event in the lifecycle of a container +type Hook struct { + Path string `json:"path"` + Args []string `json:"args,omitempty"` + Env []string `json:"env,omitempty"` +} + +// Hooks for container setup and teardown +type Hooks struct { + // Prestart is a list of hooks to be run before the container process is executed. + // On Linux, they are run after the container namespaces are created. + Prestart []Hook `json:"prestart,omitempty"` + // Poststart is a list of hooks to be run after the container process is started. + Poststart []Hook `json:"poststart,omitempty"` + // Poststop is a list of hooks to be run after the container process exits. + Poststop []Hook `json:"poststop,omitempty"` } diff --git a/vendor/src/github.com/opencontainers/specs/config.md b/vendor/src/github.com/opencontainers/specs/config.md index a3c132a..884fe32 100644 --- a/vendor/src/github.com/opencontainers/specs/config.md +++ b/vendor/src/github.com/opencontainers/specs/config.md @@ -8,14 +8,17 @@ This includes the process to run, environment variables to inject, sandboxing fe Below is a detailed description of each field defined in the configuration format. -## Manifest version +## Specification version -* **`version`** (string, required) must be in [SemVer v2.0.0](http://semver.org/spec/v2.0.0.html) format and specifies the version of the OCF specification with which the container bundle complies. The Open Container spec follows semantic versioning and retains forward and backward compatibility within major versions. For example, if an implementation is compliant with version 1.0.1 of the spec, it is compatible with the complete 1.x series. +* **`ociVersion`** (string, required) must be in [SemVer v2.0.0](http://semver.org/spec/v2.0.0.html) format and specifies the version of the OpenContainer specification with which the bundle complies. +The OpenContainer spec follows semantic versioning and retains forward and backward compatibility within major versions. +For example, if an implementation is compliant with version 1.0.1 of the spec, it is compatible with the complete 1.x series. +NOTE that there is no guarantee for forward or backward compatibility for version 0.x. *Example* ```json - "version": "0.1.0" + "ociVersion": "0.1.0" ``` ## Root Configuration @@ -34,42 +37,56 @@ Each container has exactly one *root filesystem*, specified in the *root* object } ``` -## Mount Points +## Mounts You can add array of mount points inside container as `mounts`. -Each record in this array must have configuration in [runtime config](runtime-config.md#mount-configuration). The runtime MUST mount entries in the listed order. +The parameters are similar to the ones in [the Linux mount system call](http://man7.org/linux/man-pages/man2/mount.2.html). -* **`name`** (string, required) Name of mount point. Used for config lookup. -* **`path`** (string, required) Destination of mount point: path inside container. +* **`destination`** (string, required) Destination of mount point: path inside container. +* **`type`** (string, required) Linux, *filesystemtype* argument supported by the kernel are listed in */proc/filesystems* (e.g., "minix", "ext2", "ext3", "jfs", "xfs", "reiserfs", "msdos", "proc", "nfs", "iso9660"). Windows: ntfs +* **`source`** (string, required) a device name, but can also be a directory name or a dummy. Windows, the volume name that is the target of the mount point. \\?\Volume\{GUID}\ (on Windows source is called target) +* **`options`** (list of strings, optional) in the fstab format [https://wiki.archlinux.org/index.php/Fstab](https://wiki.archlinux.org/index.php/Fstab). -*Example* +### Linux Example ```json "mounts": [ { - "name": "proc", - "path": "/proc" + "destination": "/tmp", + "type": "tmpfs", + "source": "tmpfs", + "options": ["nosuid","strictatime","mode=755","size=65536k"] }, { - "name": "dev", - "path": "/dev" - }, - { - "name": "devpts", - "path": "/dev/pts" - }, - { - "name": "data", - "path": "/data" + "destination": "/data", + "type": "bind", + "source": "/volumes/testing", + "options": ["rbind","rw"] } ] ``` +### Windows Example + +```json +"mounts": [ + "myfancymountpoint": { + "destination": "C:\\Users\\crosbymichael\\My Fancy Mount Point\\", + "type": "ntfs", + "source": "\\\\?\\Volume\\{2eca078d-5cbc-43d3-aff8-7e8511f60d0e}\\", + "options": [] + } +] +``` + +See links for details about [mountvol](http://ss64.com/nt/mountvol.html) and [SetVolumeMountPoint](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365561(v=vs.85).aspx) in Windows. + + ## Process configuration * **`terminal`** (bool, optional) specifies whether you want a terminal attached to that process. Defaults to false. -* **`cwd`** (string, optional) is the working directory that will be set for the executable. +* **`cwd`** (string, required) is the working directory that will be set for the executable. This value MUST be an absolute path. * **`env`** (array of strings, optional) contains a list of variables that will be set in the process's environment prior to execution. Elements in the array are specified as Strings in the form "KEY=value". The left hand side must consist solely of letters, digits, and underscores `_` as outlined in [IEEE Std 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html). * **`args`** (string, required) executable to launch and any flags as an array. The executable is the first element and must be available at the given path inside of the rootfs. If the executable path is not an absolute path then the search $PATH is interpreted to find the executable. @@ -127,4 +144,72 @@ For Linux-based systems the user structure has the following fields: Interpretation of the platform section of the JSON file is used to find which platform-specific sections may be available in the document. For example, if `os` is set to `linux`, then a JSON object conforming to the [Linux-specific schema](config-linux.md) SHOULD be found at the key `linux` in the `config.json`. +## Hooks + +Lifecycle hooks allow custom events for different points in a container's runtime. +Presently there are `Prestart`, `Poststart` and `Poststop`. + +* [`Prestart`](#prestart) is a list of hooks to be run before the container process is executed +* [`Poststart`](#poststart) is a list of hooks to be run immediately after the container process is started +* [`Poststop`](#poststop) is a list of hooks to be run after the container process exits + +Hooks allow one to run code before/after various lifecycle events of the container. +Hooks MUST be called in the listed order. +The state of the container is passed to the hooks over stdin, so the hooks could get the information they need to do their work. + +Hook paths are absolute and are executed from the host's filesystem. + +### Prestart + +The pre-start hooks are called after the container process is spawned, but before the user supplied command is executed. +They are called after the container namespaces are created on Linux, so they provide an opportunity to customize the container. +In Linux, for e.g., the network namespace could be configured in this hook. + +If a hook returns a non-zero exit code, then an error including the exit code and the stderr is returned to the caller and the container is torn down. + +### Poststart + +The post-start hooks are called after the user process is started. +For example this hook can notify user that real process is spawned. + +If a hook returns a non-zero exit code, then an error is logged and the remaining hooks are executed. + +### Poststop + +The post-stop hooks are called after the container process is stopped. +Cleanup or debugging could be performed in such a hook. +If a hook returns a non-zero exit code, then an error is logged and the remaining hooks are executed. + +*Example* + +```json + "hooks" : { + "prestart": [ + { + "path": "/usr/bin/fix-mounts", + "args": ["fix-mounts", "arg1", "arg2"], + "env": [ "key1=value1"] + }, + { + "path": "/usr/bin/setup-network" + } + ], + "poststart": [ + { + "path": "/usr/bin/notify-start" + } + ], + "poststop": [ + { + "path": "/usr/sbin/cleanup.sh", + "args": ["cleanup.sh", "-f"] + } + ] + } +``` + +`path` is required for a hook. +`args` and `env` are optional. +The semantics are the same as `Path`, `Args` and `Env` in [golang Cmd](https://golang.org/pkg/os/exec/#Cmd). + [uts-namespace]: http://man7.org/linux/man-pages/man7/namespaces.7.html diff --git a/vendor/src/github.com/opencontainers/specs/config_linux.go b/vendor/src/github.com/opencontainers/specs/config_linux.go index a715bb5..7202711 100644 --- a/vendor/src/github.com/opencontainers/specs/config_linux.go +++ b/vendor/src/github.com/opencontainers/specs/config_linux.go @@ -1,5 +1,10 @@ package specs +import "os" + +// LinuxStateDirectory holds the container's state information +const LinuxStateDirectory = "/run/opencontainer/containers" + // LinuxSpec is the full specification for linux containers. type LinuxSpec struct { Spec @@ -11,6 +16,35 @@ type LinuxSpec struct { type Linux struct { // Capabilities are linux capabilities that are kept for the container. Capabilities []string `json:"capabilities"` + // UIDMapping specifies user mappings for supporting user namespaces on linux. + UIDMappings []IDMapping `json:"uidMappings,omitempty"` + // GIDMapping specifies group mappings for supporting user namespaces on linux. + GIDMappings []IDMapping `json:"gidMappings,omitempty"` + // Rlimits specifies rlimit options to apply to the container's process. + Rlimits []Rlimit `json:"rlimits,omitempty"` + // Sysctl are a set of key value pairs that are set for the container on start + Sysctl map[string]string `json:"sysctl,omitempty"` + // Resources contain cgroup information for handling resource constraints + // for the container + Resources *Resources `json:"resources,omitempty"` + // CgroupsPath specifies the path to cgroups that are created and/or joined by the container. + // The path is expected to be relative to the cgroups mountpoint. + // If resources are specified, the cgroups at CgroupsPath will be updated based on resources. + CgroupsPath *string `json:"cgroupsPath,omitempty"` + // Namespaces contains the namespaces that are created and/or joined by the container + Namespaces []Namespace `json:"namespaces"` + // Devices are a list of device nodes that are created for the container + Devices []Device `json:"devices"` + // ApparmorProfile specified the apparmor profile for the container. + ApparmorProfile string `json:"apparmorProfile"` + // SelinuxProcessLabel specifies the selinux context that the container process is run as. + SelinuxProcessLabel string `json:"selinuxProcessLabel"` + // Seccomp specifies the seccomp security settings for the container. + Seccomp Seccomp `json:"seccomp"` + // RootfsPropagation is the rootfs mount propagation mode for the container. + RootfsPropagation string `json:"rootfsPropagation,omitempty"` + // NoNewPrivileges controls whether additional privileges could be gained by processes in the container. + NoNewPrivileges bool `json:"noNewPrivileges,omitempty"` } // User specifies linux specific user and group information for the container's @@ -23,3 +57,277 @@ type User struct { // AdditionalGids are additional group ids set for the container's process. AdditionalGids []uint32 `json:"additionalGids,omitempty"` } + +// Namespace is the configuration for a linux namespace +type Namespace struct { + // Type is the type of Linux namespace + Type NamespaceType `json:"type"` + // Path is a path to an existing namespace persisted on disk that can be joined + // and is of the same type + Path string `json:"path,omitempty"` +} + +// NamespaceType is one of the linux namespaces +type NamespaceType string + +const ( + // PIDNamespace for isolating process IDs + PIDNamespace NamespaceType = "pid" + // NetworkNamespace for isolating network devices, stacks, ports, etc + NetworkNamespace = "network" + // MountNamespace for isolating mount points + MountNamespace = "mount" + // IPCNamespace for isolating System V IPC, POSIX message queues + IPCNamespace = "ipc" + // UTSNamespace for isolating hostname and NIS domain name + UTSNamespace = "uts" + // UserNamespace for isolating user and group IDs + UserNamespace = "user" +) + +// IDMapping specifies UID/GID mappings +type IDMapping struct { + // HostID is the UID/GID of the host user or group + HostID uint32 `json:"hostID"` + // ContainerID is the UID/GID of the container's user or group + ContainerID uint32 `json:"containerID"` + // Size is the length of the range of IDs mapped between the two namespaces + Size uint32 `json:"size"` +} + +// Rlimit type and restrictions +type Rlimit struct { + // Type of the rlimit to set + Type string `json:"type"` + // Hard is the hard limit for the specified type + Hard uint64 `json:"hard"` + // Soft is the soft limit for the specified type + Soft uint64 `json:"soft"` +} + +// HugepageLimit structure corresponds to limiting kernel hugepages +type HugepageLimit struct { + // Pagesize is the hugepage size + Pagesize *string `json:"pageSize,omitempty"` + // Limit is the limit of "hugepagesize" hugetlb usage + Limit *uint64 `json:"limit,omitempty"` +} + +// InterfacePriority for network interfaces +type InterfacePriority struct { + // Name is the name of the network interface + Name string `json:"name"` + // Priority for the interface + Priority uint32 `json:"priority"` +} + +// blockIODevice holds major:minor format supported in blkio cgroup +type blockIODevice struct { + // Major is the device's major number. + Major int64 `json:"major"` + // Minor is the device's minor number. + Minor int64 `json:"minor"` +} + +// WeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice +type WeightDevice struct { + blockIODevice + // Weight is the bandwidth rate for the device, range is from 10 to 1000 + Weight *uint16 `json:"weight,omitempty"` + // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + LeafWeight *uint16 `json:"leafWeight,omitempty"` +} + +// ThrottleDevice struct holds a `major:minor rate_per_second` pair +type ThrottleDevice struct { + blockIODevice + // Rate is the IO rate limit per cgroup per device + Rate *uint64 `json:"rate,omitempty"` +} + +// BlockIO for Linux cgroup 'blkio' resource management +type BlockIO struct { + // Specifies per cgroup weight, range is from 10 to 1000 + Weight *uint16 `json:"blkioWeight,omitempty"` + // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + LeafWeight *uint16 `json:"blkioLeafWeight,omitempty"` + // Weight per cgroup per device, can override BlkioWeight + WeightDevice []WeightDevice `json:"blkioWeightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second + ThrottleReadBpsDevice []ThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second + ThrottleWriteBpsDevice []ThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second + ThrottleReadIOPSDevice []ThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second + ThrottleWriteIOPSDevice []ThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` +} + +// Memory for Linux cgroup 'memory' resource management +type Memory struct { + // Memory limit (in bytes). + Limit *uint64 `json:"limit,omitempty"` + // Memory reservation or soft_limit (in bytes). + Reservation *uint64 `json:"reservation,omitempty"` + // Total memory limit (memory + swap). + Swap *uint64 `json:"swap,omitempty"` + // Kernel memory limit (in bytes). + Kernel *uint64 `json:"kernel,omitempty"` + // Kernel memory limit for tcp (in bytes) + KernelTCP *uint64 `json:"kernelTCP"` + // How aggressive the kernel will swap memory pages. Range from 0 to 100. + Swappiness *uint64 `json:"swappiness,omitempty"` +} + +// CPU for Linux cgroup 'cpu' resource management +type CPU struct { + // CPU shares (relative weight (ratio) vs. other cgroups with cpu shares). + Shares *uint64 `json:"shares,omitempty"` + // CPU hardcap limit (in usecs). Allowed cpu time in a given period. + Quota *uint64 `json:"quota,omitempty"` + // CPU period to be used for hardcapping (in usecs). + Period *uint64 `json:"period,omitempty"` + // How much time realtime scheduling may use (in usecs). + RealtimeRuntime *uint64 `json:"realtimeRuntime,omitempty"` + // CPU period to be used for realtime scheduling (in usecs). + RealtimePeriod *uint64 `json:"realtimePeriod,omitempty"` + // CPUs to use within the cpuset. Default is to use any CPU available. + Cpus *string `json:"cpus,omitempty"` + // List of memory nodes in the cpuset. Default is to use any available memory node. + Mems *string `json:"mems,omitempty"` +} + +// Pids for Linux cgroup 'pids' resource management (Linux 4.3) +type Pids struct { + // Maximum number of PIDs. Default is "no limit". + Limit *int64 `json:"limit,omitempty"` +} + +// Network identification and priority configuration +type Network struct { + // Set class identifier for container's network packets + ClassID *uint32 `json:"classID"` + // Set priority of network traffic for container + Priorities []InterfacePriority `json:"priorities,omitempty"` +} + +// Resources has container runtime resource constraints +type Resources struct { + // Devices are a list of device rules for the whitelist controller + Devices []DeviceCgroup `json:"devices"` + // DisableOOMKiller disables the OOM killer for out of memory conditions + DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` + // Specify an oom_score_adj for the container. + OOMScoreAdj *int `json:"oomScoreAdj,omitempty"` + // Memory restriction configuration + Memory *Memory `json:"memory,omitempty"` + // CPU resource restriction configuration + CPU *CPU `json:"cpu,omitempty"` + // Task resource restriction configuration. + Pids *Pids `json:"pids,omitempty"` + // BlockIO restriction configuration + BlockIO *BlockIO `json:"blockIO,omitempty"` + // Hugetlb limit (in bytes) + HugepageLimits []HugepageLimit `json:"hugepageLimits,omitempty"` + // Network restriction configuration + Network *Network `json:"network,omitempty"` +} + +// Device represents the mknod information for a Linux special device file +type Device struct { + // Path to the device. + Path string `json:"path"` + // Device type, block, char, etc. + Type rune `json:"type"` + // Major is the device's major number. + Major int64 `json:"major"` + // Minor is the device's minor number. + Minor int64 `json:"minor"` + // FileMode permission bits for the device. + FileMode *os.FileMode `json:"fileMode,omitempty"` + // UID of the device. + UID *uint32 `json:"uid,omitempty"` + // Gid of the device. + GID *uint32 `json:"gid,omitempty"` +} + +// DeviceCgroup represents a device rule for the whitelist controller +type DeviceCgroup struct { + // Allow or deny + Allow bool `json:"allow"` + // Device type, block, char, etc. + Type *rune `json:"type,omitempty"` + // Major is the device's major number. + Major *int64 `json:"major,omitempty"` + // Minor is the device's minor number. + Minor *int64 `json:"minor,omitempty"` + // Cgroup access permissions format, rwm. + Access *string `json:"access,omitempty"` +} + +// Seccomp represents syscall restrictions +type Seccomp struct { + DefaultAction Action `json:"defaultAction"` + Architectures []Arch `json:"architectures"` + Syscalls []Syscall `json:"syscalls,omitempty"` +} + +// Arch used for additional architectures +type Arch string + +// Additional architectures permitted to be used for system calls +// By default only the native architecture of the kernel is permitted +const ( + ArchX86 Arch = "SCMP_ARCH_X86" + ArchX86_64 Arch = "SCMP_ARCH_X86_64" + ArchX32 Arch = "SCMP_ARCH_X32" + ArchARM Arch = "SCMP_ARCH_ARM" + ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" + ArchMIPS Arch = "SCMP_ARCH_MIPS" + ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" + ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" + ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" + ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" + ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" +) + +// Action taken upon Seccomp rule match +type Action string + +// Define actions for Seccomp rules +const ( + ActKill Action = "SCMP_ACT_KILL" + ActTrap Action = "SCMP_ACT_TRAP" + ActErrno Action = "SCMP_ACT_ERRNO" + ActTrace Action = "SCMP_ACT_TRACE" + ActAllow Action = "SCMP_ACT_ALLOW" +) + +// Operator used to match syscall arguments in Seccomp +type Operator string + +// Define operators for syscall arguments in Seccomp +const ( + OpNotEqual Operator = "SCMP_CMP_NE" + OpLessThan Operator = "SCMP_CMP_LT" + OpLessEqual Operator = "SCMP_CMP_LE" + OpEqualTo Operator = "SCMP_CMP_EQ" + OpGreaterEqual Operator = "SCMP_CMP_GE" + OpGreaterThan Operator = "SCMP_CMP_GT" + OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ" +) + +// Arg used for matching specific syscall arguments in Seccomp +type Arg struct { + Index uint `json:"index"` + Value uint64 `json:"value"` + ValueTwo uint64 `json:"valueTwo"` + Op Operator `json:"op"` +} + +// Syscall is used to match a syscall in Seccomp +type Syscall struct { + Name string `json:"name"` + Action Action `json:"action"` + Args []Arg `json:"args,omitempty"` +} diff --git a/vendor/src/github.com/opencontainers/specs/glossary.md b/vendor/src/github.com/opencontainers/specs/glossary.md index 0007562..f9d11c4 100644 --- a/vendor/src/github.com/opencontainers/specs/glossary.md +++ b/vendor/src/github.com/opencontainers/specs/glossary.md @@ -6,7 +6,7 @@ A [directory structure](bundle.md) that is written ahead of time, distributed, a ## Configuration -The [`config.json`](config.md) and [`runtime.json`](runtime-config.md) files in a [bundle](#bundle) which define the intended [container](#container) and container process. +The [`config.json`](config.md) file in a [bundle](#bundle) which defines the intended [container](#container) and container process. ## Container diff --git a/vendor/src/github.com/opencontainers/specs/runtime-config-linux.md b/vendor/src/github.com/opencontainers/specs/runtime-config-linux.md deleted file mode 100644 index 651596b..0000000 --- a/vendor/src/github.com/opencontainers/specs/runtime-config-linux.md +++ /dev/null @@ -1,535 +0,0 @@ -# Linux-specific Runtime Configuration - -## Namespaces - -A namespace wraps a global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource. -Changes to the global resource are visible to other processes that are members of the namespace, but are invisible to other processes. -For more information, see [the man page](http://man7.org/linux/man-pages/man7/namespaces.7.html). - -Namespaces are specified as an array of entries inside the `namespaces` root field. -The following parameters can be specified to setup namespaces: - -* **`type`** *(string, required)* - namespace type. The following namespaces types are supported: - * **`pid`** processes inside the container will only be able to see other processes inside the same container - * **`network`** the container will have its own network stack - * **`mount`** the container will have an isolated mount table - * **`ipc`** processes inside the container will only be able to communicate to other processes inside the same container via system level IPC - * **`uts`** the container will be able to have its own hostname and domain name - * **`user`** the container will be able to remap user and group IDs from the host to local users and groups within the container - -* **`path`** *(string, optional)* - path to namespace file - -If a path is specified, that particular file is used to join that type of namespace. -Also, when a path is specified, a runtime MUST assume that the setup for that particular namespace has already been done and error out if the config specifies anything else related to that namespace. - -###### Example - -```json - "namespaces": [ - { - "type": "pid", - "path": "/proc/1234/ns/pid" - }, - { - "type": "network", - "path": "/var/run/netns/neta" - }, - { - "type": "mount" - }, - { - "type": "ipc" - }, - { - "type": "uts" - }, - { - "type": "user" - } - ] -``` - -## User namespace mappings - -###### Example - -```json - "uidMappings": [ - { - "hostID": 1000, - "containerID": 0, - "size": 10 - } - ], - "gidMappings": [ - { - "hostID": 1000, - "containerID": 0, - "size": 10 - } - ] -``` - -uid/gid mappings describe the user namespace mappings from the host to the container. -The mappings represent how the bundle `rootfs` expects the user namespace to be setup and the runtime SHOULD NOT modify the permissions on the rootfs to realize the mapping. -*hostID* is the starting uid/gid on the host to be mapped to *containerID* which is the starting uid/gid in the container and *size* refers to the number of ids to be mapped. -There is a limit of 5 mappings which is the Linux kernel hard limit. - -## Devices - -`devices` is an array specifying the list of devices to be created in the container. - -The following parameters can be specified: - -* **`type`** *(char, required)* - type of device: `c`, `b`, `u` or `p`. More info in `man mknod`. - -* **`path`** *(string, optional)* - full path to device inside container - -* **`major, minor`** *(int64, required)* - major, minor numbers for device. More info in `man mknod`. There is a special value: `-1`, which means `*` for `device` cgroup setup. - -* **`permissions`** *(string, optional)* - cgroup permissions for device. A composition of `r` (*read*), `w` (*write*), and `m` (*mknod*). - -* **`fileMode`** *(uint32, optional)* - file mode for device file - -* **`uid`** *(uint32, optional)* - uid of device owner - -* **`gid`** *(uint32, optional)* - gid of device owner - -**`fileMode`**, **`uid`** and **`gid`** are required if **`path`** is given and are otherwise not allowed. - -###### Example - -```json - "devices": [ - { - "path": "/dev/random", - "type": "c", - "major": 1, - "minor": 8, - "permissions": "rwm", - "fileMode": 0666, - "uid": 0, - "gid": 0 - }, - { - "path": "/dev/urandom", - "type": "c", - "major": 1, - "minor": 9, - "permissions": "rwm", - "fileMode": 0666, - "uid": 0, - "gid": 0 - }, - { - "path": "/dev/null", - "type": "c", - "major": 1, - "minor": 3, - "permissions": "rwm", - "fileMode": 0666, - "uid": 0, - "gid": 0 - }, - { - "path": "/dev/zero", - "type": "c", - "major": 1, - "minor": 5, - "permissions": "rwm", - "fileMode": 0666, - "uid": 0, - "gid": 0 - }, - { - "path": "/dev/tty", - "type": "c", - "major": 5, - "minor": 0, - "permissions": "rwm", - "fileMode": 0666, - "uid": 0, - "gid": 0 - }, - { - "path": "/dev/full", - "type": "c", - "major": 1, - "minor": 7, - "permissions": "rwm", - "fileMode": 0666, - "uid": 0, - "gid": 0 - } - ] -``` - -## Control groups - -Also known as cgroups, they are used to restrict resource usage for a container and handle device access. -cgroups provide controls to restrict cpu, memory, IO, pids and network for the container. -For more information, see the [kernel cgroups documentation](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt). - -The path to the cgroups can be specified in the Spec via `cgroupsPath`. -`cgroupsPath` is expected to be relative to the cgroups mount point. -If `cgroupsPath` is not specified, implementations can define the default cgroup path. -Implementations of the Spec can choose to name cgroups in any manner. -The Spec does not include naming schema for cgroups. -The Spec does not support [split hierarchy](https://www.kernel.org/doc/Documentation/cgroups/unified-hierarchy.txt). -The cgroups will be created if they don't exist. - -###### Example - -```json - "cgroupsPath": "/myRuntime/myContainer" -``` - -`cgroupsPath` can be used to either control the cgroups hierarchy for containers or to run a new process in an existing container. - -You can configure a container's cgroups via the `resources` field of the Linux configuration. -Do not specify `resources` unless limits have to be updated. -For example, to run a new process in an existing container without updating limits, `resources` need not be specified. - -#### Disable out-of-memory killer - -`disableOOMKiller` contains a boolean (`true` or `false`) that enables or disables the Out of Memory killer for a cgroup. -If enabled (`false`), tasks that attempt to consume more memory than they are allowed are immediately killed by the OOM killer. -The OOM killer is enabled by default in every cgroup using the `memory` subsystem. -To disable it, specify a value of `true`. -For more information, see [the memory cgroup man page](https://www.kernel.org/doc/Documentation/cgroups/memory.txt). - -* **`disableOOMKiller`** *(bool, optional)* - enables or disables the OOM killer - -###### Example - -```json - "disableOOMKiller": false -``` - -#### Set oom_score_adj - -More information on `oom_score_adj` available [here](https://www.kernel.org/doc/Documentation/filesystems/proc.txt). - -###### Example - -```json - "oomScoreAdj": 0 -``` - -#### Memory - -`memory` represents the cgroup subsystem `memory` and it's used to set limits on the container's memory usage. -For more information, see [the memory cgroup man page](https://www.kernel.org/doc/Documentation/cgroups/memory.txt). - -The following parameters can be specified to setup the controller: - -* **`limit`** *(uint64, optional)* - sets limit of memory usage - -* **`reservation`** *(uint64, optional)* - sets soft limit of memory usage - -* **`swap`** *(uint64, optional)* - sets limit of memory+Swap usage - -* **`kernel`** *(uint64, optional)* - sets hard limit for kernel memory - -* **`kernelTCP`** *(uint64, optional)* - sets hard limit for kernel memory in tcp using - -* **`swappiness`** *(uint64, optional)* - sets swappiness parameter of vmscan (See sysctl's vm.swappiness) - -###### Example - -```json - "memory": { - "limit": 0, - "reservation": 0, - "swap": 0, - "kernel": 0, - "kernelTCP": 0, - "swappiness": 0 - } -``` - -#### CPU - -`cpu` represents the cgroup subsystems `cpu` and `cpusets`. -For more information, see [the cpusets cgroup man page](https://www.kernel.org/doc/Documentation/cgroups/cpusets.txt). - -The following parameters can be specified to setup the controller: - -* **`shares`** *(uint64, optional)* - specifies a relative share of CPU time available to the tasks in a cgroup - -* **`quota`** *(uint64, optional)* - specifies the total amount of time in microseconds for which all tasks in a cgroup can run during one period (as defined by **`period`** below) - -* **`period`** *(uint64, optional)* - specifies a period of time in microseconds for how regularly a cgroup's access to CPU resources should be reallocated (CFS scheduler only) - -* **`realtimeRuntime`** *(uint64, optional)* - specifies a period of time in microseconds for the longest continuous period in which the tasks in a cgroup have access to CPU resources - -* **`realtimePeriod`** *(uint64, optional)* - same as **`period`** but applies to realtime scheduler only - -* **`cpus`** *(string, optional)* - list of CPUs the container will run in - -* **`mems`** *(string, optional)* - list of Memory Nodes the container will run in - -###### Example - -```json - "cpu": { - "shares": 0, - "quota": 0, - "period": 0, - "realtimeRuntime": 0, - "realtimePeriod": 0, - "cpus": "", - "mems": "" - } -``` - -#### Block IO Controller - -`blockIO` represents the cgroup subsystem `blkio` which implements the block io controller. -For more information, see [the kernel cgroups documentation about blkio](https://www.kernel.org/doc/Documentation/cgroups/blkio-controller.txt). - -The following parameters can be specified to setup the controller: - -* **`blkioWeight`** *(uint16, optional)* - specifies per-cgroup weight. This is default weight of the group on all devices until and unless overridden by per-device rules. The range is from 10 to 1000. - -* **`blkioLeafWeight`** *(uint16, optional)* - equivalents of `blkioWeight` for the purpose of deciding how much weight tasks in the given cgroup has while competing with the cgroup's child cgroups. The range is from 10 to 1000. - -* **`blkioWeightDevice`** *(array, optional)* - specifies the list of devices which will be bandwidth rate limited. The following parameters can be specified per-device: - * **`major, minor`** *(int64, required)* - major, minor numbers for device. More info in `man mknod`. - * **`weight`** *(uint16, optional)* - bandwidth rate for the device, range is from 10 to 1000 - * **`leafWeight`** *(uint16, optional)* - bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only - - You must specify at least one of `weight` or `leafWeight` in a given entry, and can specify both. - -* **`blkioThrottleReadBpsDevice`**, **`blkioThrottleWriteBpsDevice`**, **`blkioThrottleReadIOPSDevice`**, **`blkioThrottleWriteIOPSDevice`** *(array, optional)* - specify the list of devices which will be IO rate limited. The following parameters can be specified per-device: - * **`major, minor`** *(int64, required)* - major, minor numbers for device. More info in `man mknod`. - * **`rate`** *(uint64, required)* - IO rate limit for the device - -###### Example - -```json - "blockIO": { - "blkioWeight": 0, - "blkioLeafWeight": 0, - "blkioWeightDevice": [ - { - "major": 8, - "minor": 0, - "weight": 500, - "leafWeight": 300 - }, - { - "major": 8, - "minor": 16, - "weight": 500 - } - ], - "blkioThrottleReadBpsDevice": [ - { - "major": 8, - "minor": 0, - "rate": 600 - } - ], - "blkioThrottleWriteIOPSDevice": [ - { - "major": 8, - "minor": 16, - "rate": 300 - } - ] - } -``` - -#### Huge page limits - -`hugepageLimits` represents the `hugetlb` controller which allows to limit the -HugeTLB usage per control group and enforces the controller limit during page fault. -For more information, see the [kernel cgroups documentation about HugeTLB](https://www.kernel.org/doc/Documentation/cgroups/hugetlb.txt). - -`hugepageLimits` is an array of entries, each having the following structure: - -* **`pageSize`** *(string, required)* - hugepage size - -* **`limit`** *(uint64, required)* - limit in bytes of *hugepagesize* HugeTLB usage - -###### Example - -```json - "hugepageLimits": [ - { - "pageSize": "2MB", - "limit": 9223372036854771712 - } - ] -``` - -#### Network - -`network` represents the cgroup subsystems `net_cls` and `net_prio`. -For more information, see [the net\_cls cgroup man page](https://www.kernel.org/doc/Documentation/cgroups/net_cls.txt) and [the net\_prio cgroup man page](https://www.kernel.org/doc/Documentation/cgroups/net_prio.txt). - -The following parameters can be specified to setup these cgroup controllers: - -* **`classID`** *(string, optional)* - is the network class identifier the cgroup's network packets will be tagged with - -* **`priorities`** *(array, optional)* - specifies a list of objects of the priorities assigned to traffic originating from -processes in the group and egressing the system on various interfaces. The following parameters can be specified per-priority: - * **`name`** *(string, required)* - interface name - * **`priority`** *(uint32, required)* - priority applied to the interface - -###### Example - -```json - "network": { - "classID": "0x100001", - "priorities": [ - { - "name": "eth0", - "priority": 500 - }, - { - "name": "eth1", - "priority": 1000 - } - ] - } -``` - -#### PIDs - -`pids` represents the cgroup subsystem `pids`. -For more information, see [the pids cgroup man page](https://www.kernel.org/doc/Documentation/cgroups/pids.txt -). - -The following paramters can be specified to setup the controller: - -* **`limit`** *(int64, required)* - specifies the maximum number of tasks in the cgroup - -###### Example - -```json - "pids": { - "limit": 32771 - } -``` - -## Sysctl - -sysctl allows kernel parameters to be modified at runtime for the container. -For more information, see [the man page](http://man7.org/linux/man-pages/man8/sysctl.8.html) - -###### Example - -```json - "sysctl": { - "net.ipv4.ip_forward": "1", - "net.core.somaxconn": "256" - } -``` - -## Rlimits - -rlimits allow setting resource limits. -`type` is a string with a value from those defined in [the man page](http://man7.org/linux/man-pages/man2/setrlimit.2.html). -The kernel enforces the `soft` limit for a resource while the `hard` limit acts as a ceiling for that value that could be set by an unprivileged process. - -###### Example - -```json - "rlimits": [ - { - "type": "RLIMIT_NPROC", - "soft": 1024, - "hard": 102400 - } - ] -``` - -## SELinux process label - -SELinux process label specifies the label with which the processes in a container are run. -For more information about SELinux, see [Selinux documentation](http://selinuxproject.org/page/Main_Page) - -###### Example - -```json - "selinuxProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c124,c675" -``` - -## Apparmor profile - -Apparmor profile specifies the name of the apparmor profile that will be used for the container. -For more information about Apparmor, see [Apparmor documentation](https://wiki.ubuntu.com/AppArmor) - -###### Example - -```json - "apparmorProfile": "acme_secure_profile" -``` - -## seccomp - -Seccomp provides application sandboxing mechanism in the Linux kernel. -Seccomp configuration allows one to configure actions to take for matched syscalls and furthermore also allows matching on values passed as arguments to syscalls. -For more information about Seccomp, see [Seccomp kernel documentation](https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt) -The actions, architectures, and operators are strings that match the definitions in seccomp.h from [libseccomp](https://github.com/seccomp/libseccomp) and are translated to corresponding values. -A valid list of constants as of Libseccomp v2.2.3 is contained below. - -Architecture Constants -* `SCMP_ARCH_X86` -* `SCMP_ARCH_X86_64` -* `SCMP_ARCH_X32` -* `SCMP_ARCH_ARM` -* `SCMP_ARCH_AARCH64` -* `SCMP_ARCH_MIPS` -* `SCMP_ARCH_MIPS64` -* `SCMP_ARCH_MIPS64N32` -* `SCMP_ARCH_MIPSEL` -* `SCMP_ARCH_MIPSEL64` -* `SCMP_ARCH_MIPSEL64N32` - -Action Constants: -* `SCMP_ACT_KILL` -* `SCMP_ACT_TRAP` -* `SCMP_ACT_ERRNO` -* `SCMP_ACT_TRACE` -* `SCMP_ACT_ALLOW` - -Operator Constants: -* `SCMP_CMP_NE` -* `SCMP_CMP_LT` -* `SCMP_CMP_LE` -* `SCMP_CMP_EQ` -* `SCMP_CMP_GE` -* `SCMP_CMP_GT` -* `SCMP_CMP_MASKED_EQ` - -###### Example - -```json - "seccomp": { - "defaultAction": "SCMP_ACT_ALLOW", - "architectures": [ - "SCMP_ARCH_X86" - ], - "syscalls": [ - { - "name": "getcwd", - "action": "SCMP_ACT_ERRNO" - } - ] - } -``` - -## Rootfs Mount Propagation - -rootfsPropagation sets the rootfs's mount propagation. -Its value is either slave, private, or shared. -[The kernel doc](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt) has more information about mount propagation. - -###### Example - -```json - "rootfsPropagation": "slave", -``` diff --git a/vendor/src/github.com/opencontainers/specs/runtime-config.md b/vendor/src/github.com/opencontainers/specs/runtime-config.md deleted file mode 100644 index a02a08b..0000000 --- a/vendor/src/github.com/opencontainers/specs/runtime-config.md +++ /dev/null @@ -1,122 +0,0 @@ -# Runtime Configuration - -## Hooks - -Lifecycle hooks allow custom events for different points in a container's runtime. -Presently there are `Prestart`, `Poststart` and `Poststop`. - -* [`Prestart`](#prestart) is a list of hooks to be run before the container process is executed -* [`Poststart`](#poststart) is a list of hooks to be run immediately after the container process is started -* [`Poststop`](#poststop) is a list of hooks to be run after the container process exits - -Hooks allow one to run code before/after various lifecycle events of the container. -Hooks MUST be called in the listed order. -The state of the container is passed to the hooks over stdin, so the hooks could get the information they need to do their work. - -Hook paths are absolute and are executed from the host's filesystem. - -### Prestart - -The pre-start hooks are called after the container process is spawned, but before the user supplied command is executed. -They are called after the container namespaces are created on Linux, so they provide an opportunity to customize the container. -In Linux, for e.g., the network namespace could be configured in this hook. - -If a hook returns a non-zero exit code, then an error including the exit code and the stderr is returned to the caller and the container is torn down. - -### Poststart - -The post-start hooks are called after the user process is started. -For example this hook can notify user that real process is spawned. - -If a hook returns a non-zero exit code, then an error is logged and the remaining hooks are executed. - -### Poststop - -The post-stop hooks are called after the container process is stopped. -Cleanup or debugging could be performed in such a hook. -If a hook returns a non-zero exit code, then an error is logged and the remaining hooks are executed. - -*Example* - -```json - "hooks" : { - "prestart": [ - { - "path": "/usr/bin/fix-mounts", - "args": ["fix-mounts", "arg1", "arg2"], - "env": [ "key1=value1"] - }, - { - "path": "/usr/bin/setup-network" - } - ], - "poststart": [ - { - "path": "/usr/bin/notify-start" - } - ], - "poststop": [ - { - "path": "/usr/sbin/cleanup.sh", - "args": ["cleanup.sh", "-f"] - } - ] - } -``` - -`path` is required for a hook. -`args` and `env` are optional. -The semantics are the same as `Path`, `Args` and `Env` in [golang Cmd](https://golang.org/pkg/os/exec/#Cmd). - -## Mount Configuration - -Additional filesystems can be declared as "mounts", specified in the *mounts* object. -Keys in this object are names of mount points from portable config. -Values are objects with configuration of mount points. -The parameters are similar to the ones in [the Linux mount system call](http://man7.org/linux/man-pages/man2/mount.2.html). -Only [mounts from the portable config](config.md#mount-points) will be mounted. - -* **`type`** (string, required) Linux, *filesystemtype* argument supported by the kernel are listed in */proc/filesystems* (e.g., "minix", "ext2", "ext3", "jfs", "xfs", "reiserfs", "msdos", "proc", "nfs", "iso9660"). Windows: ntfs -* **`source`** (string, required) a device name, but can also be a directory name or a dummy. Windows, the volume name that is the target of the mount point. \\?\Volume\{GUID}\ (on Windows source is called target) -* **`options`** (list of strings, optional) in the fstab format [https://wiki.archlinux.org/index.php/Fstab](https://wiki.archlinux.org/index.php/Fstab). - -*Example (Linux)* - -```json -"mounts": { - "proc": { - "type": "proc", - "source": "proc", - "options": [] - }, - "dev": { - "type": "tmpfs", - "source": "tmpfs", - "options": ["nosuid","strictatime","mode=755","size=65536k"] - }, - "devpts": { - "type": "devpts", - "source": "devpts", - "options": ["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"] - }, - "data": { - "type": "bind", - "source": "/volumes/testing", - "options": ["rbind","rw"] - } -} -``` - -*Example (Windows)* - -```json -"mounts": { - "myfancymountpoint": { - "type": "ntfs", - "source": "\\\\?\\Volume\\{2eca078d-5cbc-43d3-aff8-7e8511f60d0e}\\", - "options": [] - } -} -``` - -See links for details about [mountvol](http://ss64.com/nt/mountvol.html) and [SetVolumeMountPoint](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365561(v=vs.85).aspx) in Windows. diff --git a/vendor/src/github.com/opencontainers/specs/runtime.md b/vendor/src/github.com/opencontainers/specs/runtime.md index 090f9ea..12d6c94 100644 --- a/vendor/src/github.com/opencontainers/specs/runtime.md +++ b/vendor/src/github.com/opencontainers/specs/runtime.md @@ -38,8 +38,8 @@ This is provided so that consumers can find the container's configuration and ro The lifecycle describes the timeline of events that happen from when a container is created to when it ceases to exist. 1. OCI compliant runtime is invoked by passing the bundle path as argument. -2. The container's runtime environment is created according to the configuration in `config.json` and `runtime.json`. - Any updates to `config.json` or `runtime.json` after container is running do not affect the container. +2. The container's runtime environment is created according to the configuration in [`config.json`](config.md). + Any updates to `config.json` after container is running do not affect the container. 3. The container's state.json file is written to the filesystem. 4. The prestart hooks are invoked by the runtime. If any prestart hook fails, then the container is stopped and the lifecycle continues at step 8. @@ -56,4 +56,4 @@ Note: The lifecycle is a WIP and it will evolve as we have more use cases and mo ## Hooks -See [runtime configuration for hooks](./runtime-config.md) +See [runtime configuration for hooks](./config.md#hooks) diff --git a/vendor/src/github.com/opencontainers/specs/runtime_config.go b/vendor/src/github.com/opencontainers/specs/runtime_config.go deleted file mode 100644 index 931f912..0000000 --- a/vendor/src/github.com/opencontainers/specs/runtime_config.go +++ /dev/null @@ -1,42 +0,0 @@ -package specs - -// RuntimeSpec contains host-specific configuration information for -// a container. This information must not be included when the bundle -// is packaged for distribution. -type RuntimeSpec struct { - // Mounts is a mapping of names to mount configurations. - // Which mounts will be mounted and where should be chosen with MountPoints - // in Spec. - Mounts map[string]Mount `json:"mounts"` - // Hooks are the commands run at various lifecycle events of the container. - Hooks Hooks `json:"hooks"` -} - -// Hook specifies a command that is run at a particular event in the lifecycle of a container -type Hook struct { - Path string `json:"path"` - Args []string `json:"args,omitempty"` - Env []string `json:"env,omitempty"` -} - -// Hooks for container setup and teardown -type Hooks struct { - // Prestart is a list of hooks to be run before the container process is executed. - // On Linux, they are run after the container namespaces are created. - Prestart []Hook `json:"prestart,omitempty"` - // Poststart is a list of hooks to be run after the container process is started. - Poststart []Hook `json:"poststart,omitempty"` - // Poststop is a list of hooks to be run after the container process exits. - Poststop []Hook `json:"poststop,omitempty"` -} - -// Mount specifies a mount for a container -type Mount struct { - // Type specifies the mount kind. - Type string `json:"type"` - // Source specifies the source path of the mount. In the case of bind mounts on - // linux based systems this would be the file on the host. - Source string `json:"source"` - // Options are fstab style mount options. - Options []string `json:"options,omitempty"` -} diff --git a/vendor/src/github.com/opencontainers/specs/runtime_config_linux.go b/vendor/src/github.com/opencontainers/specs/runtime_config_linux.go deleted file mode 100644 index d01cc7b..0000000 --- a/vendor/src/github.com/opencontainers/specs/runtime_config_linux.go +++ /dev/null @@ -1,306 +0,0 @@ -package specs - -import "os" - -// LinuxStateDirectory holds the container's state information -const LinuxStateDirectory = "/run/opencontainer/containers" - -// LinuxRuntimeSpec is the full specification for linux containers. -type LinuxRuntimeSpec struct { - RuntimeSpec - // LinuxRuntime is platform specific configuration for linux based containers. - Linux LinuxRuntime `json:"linux"` -} - -// LinuxRuntime hosts the Linux-only runtime information -type LinuxRuntime struct { - // UIDMapping specifies user mappings for supporting user namespaces on linux. - UIDMappings []IDMapping `json:"uidMappings,omitempty"` - // GIDMapping specifies group mappings for supporting user namespaces on linux. - GIDMappings []IDMapping `json:"gidMappings,omitempty"` - // Rlimits specifies rlimit options to apply to the container's process. - Rlimits []Rlimit `json:"rlimits,omitempty"` - // Sysctl are a set of key value pairs that are set for the container on start - Sysctl map[string]string `json:"sysctl,omitempty"` - // Resources contain cgroup information for handling resource constraints - // for the container - Resources *Resources `json:"resources,omitempty"` - // CgroupsPath specifies the path to cgroups that are created and/or joined by the container. - // The path is expected to be relative to the cgroups mountpoint. - // If resources are specified, the cgroups at CgroupsPath will be updated based on resources. - CgroupsPath *string `json:"cgroupsPath,omitempty"` - // Namespaces contains the namespaces that are created and/or joined by the container - Namespaces []Namespace `json:"namespaces"` - // Devices are a list of device nodes that are created and enabled for the container - Devices []Device `json:"devices"` - // ApparmorProfile specified the apparmor profile for the container. - ApparmorProfile string `json:"apparmorProfile"` - // SelinuxProcessLabel specifies the selinux context that the container process is run as. - SelinuxProcessLabel string `json:"selinuxProcessLabel"` - // Seccomp specifies the seccomp security settings for the container. - Seccomp Seccomp `json:"seccomp"` - // RootfsPropagation is the rootfs mount propagation mode for the container - RootfsPropagation string `json:"rootfsPropagation,omitempty"` -} - -// Namespace is the configuration for a linux namespace -type Namespace struct { - // Type is the type of Linux namespace - Type NamespaceType `json:"type"` - // Path is a path to an existing namespace persisted on disk that can be joined - // and is of the same type - Path string `json:"path,omitempty"` -} - -// NamespaceType is one of the linux namespaces -type NamespaceType string - -const ( - // PIDNamespace for isolating process IDs - PIDNamespace NamespaceType = "pid" - // NetworkNamespace for isolating network devices, stacks, ports, etc - NetworkNamespace = "network" - // MountNamespace for isolating mount points - MountNamespace = "mount" - // IPCNamespace for isolating System V IPC, POSIX message queues - IPCNamespace = "ipc" - // UTSNamespace for isolating hostname and NIS domain name - UTSNamespace = "uts" - // UserNamespace for isolating user and group IDs - UserNamespace = "user" -) - -// IDMapping specifies UID/GID mappings -type IDMapping struct { - // HostID is the UID/GID of the host user or group - HostID uint32 `json:"hostID"` - // ContainerID is the UID/GID of the container's user or group - ContainerID uint32 `json:"containerID"` - // Size is the length of the range of IDs mapped between the two namespaces - Size uint32 `json:"size"` -} - -// Rlimit type and restrictions -type Rlimit struct { - // Type of the rlimit to set - Type string `json:"type"` - // Hard is the hard limit for the specified type - Hard uint64 `json:"hard"` - // Soft is the soft limit for the specified type - Soft uint64 `json:"soft"` -} - -// HugepageLimit structure corresponds to limiting kernel hugepages -type HugepageLimit struct { - // Pagesize is the hugepage size - Pagesize *string `json:"pageSize,omitempty"` - // Limit is the limit of "hugepagesize" hugetlb usage - Limit *uint64 `json:"limit,omitempty"` -} - -// InterfacePriority for network interfaces -type InterfacePriority struct { - // Name is the name of the network interface - Name string `json:"name"` - // Priority for the interface - Priority uint32 `json:"priority"` -} - -// blockIODevice holds major:minor format supported in blkio cgroup -type blockIODevice struct { - // Major is the device's major number. - Major int64 `json:"major"` - // Minor is the device's minor number. - Minor int64 `json:"minor"` -} - -// WeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice -type WeightDevice struct { - blockIODevice - // Weight is the bandwidth rate for the device, range is from 10 to 1000 - Weight *uint16 `json:"weight,omitempty"` - // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only - LeafWeight *uint16 `json:"leafWeight,omitempty"` -} - -// ThrottleDevice struct holds a `major:minor rate_per_second` pair -type ThrottleDevice struct { - blockIODevice - // Rate is the IO rate limit per cgroup per device - Rate *uint64 `json:"rate,omitempty"` -} - -// BlockIO for Linux cgroup 'blkio' resource management -type BlockIO struct { - // Specifies per cgroup weight, range is from 10 to 1000 - Weight *uint16 `json:"blkioWeight,omitempty"` - // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only - LeafWeight *uint16 `json:"blkioLeafWeight,omitempty"` - // Weight per cgroup per device, can override BlkioWeight - WeightDevice []*WeightDevice `json:"blkioWeightDevice,omitempty"` - // IO read rate limit per cgroup per device, bytes per second - ThrottleReadBpsDevice []*ThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` - // IO write rate limit per cgroup per device, bytes per second - ThrottleWriteBpsDevice []*ThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` - // IO read rate limit per cgroup per device, IO per second - ThrottleReadIOPSDevice []*ThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` - // IO write rate limit per cgroup per device, IO per second - ThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` -} - -// Memory for Linux cgroup 'memory' resource management -type Memory struct { - // Memory limit (in bytes). - Limit *uint64 `json:"limit,omitempty"` - // Memory reservation or soft_limit (in bytes). - Reservation *uint64 `json:"reservation,omitempty"` - // Total memory limit (memory + swap). - Swap *uint64 `json:"swap,omitempty"` - // Kernel memory limit (in bytes). - Kernel *uint64 `json:"kernel,omitempty"` - // Kernel memory limit for tcp (in bytes) - KernelTCP *uint64 `json:"kernelTCP"` - // How aggressive the kernel will swap memory pages. Range from 0 to 100. - Swappiness *uint64 `json:"swappiness,omitempty"` -} - -// CPU for Linux cgroup 'cpu' resource management -type CPU struct { - // CPU shares (relative weight (ratio) vs. other cgroups with cpu shares). - Shares *uint64 `json:"shares,omitempty"` - // CPU hardcap limit (in usecs). Allowed cpu time in a given period. - Quota *uint64 `json:"quota,omitempty"` - // CPU period to be used for hardcapping (in usecs). - Period *uint64 `json:"period,omitempty"` - // How much time realtime scheduling may use (in usecs). - RealtimeRuntime *uint64 `json:"realtimeRuntime,omitempty"` - // CPU period to be used for realtime scheduling (in usecs). - RealtimePeriod *uint64 `json:"realtimePeriod,omitempty"` - // CPUs to use within the cpuset. Default is to use any CPU available. - Cpus *string `json:"cpus,omitempty"` - // List of memory nodes in the cpuset. Default is to use any available memory node. - Mems *string `json:"mems,omitempty"` -} - -// Pids for Linux cgroup 'pids' resource management (Linux 4.3) -type Pids struct { - // Maximum number of PIDs. Default is "no limit". - Limit *int64 `json:"limit,omitempty"` -} - -// Network identification and priority configuration -type Network struct { - // Set class identifier for container's network packets - // this is actually a string instead of a uint64 to overcome the json - // limitation of specifying hex numbers - ClassID string `json:"classID"` - // Set priority of network traffic for container - Priorities []InterfacePriority `json:"priorities"` -} - -// Resources has container runtime resource constraints -type Resources struct { - // DisableOOMKiller disables the OOM killer for out of memory conditions - DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` - // Specify an oom_score_adj for the container. - OOMScoreAdj *int `json:"oomScoreAdj,omitempty"` - // Memory restriction configuration - Memory *Memory `json:"memory,omitempty"` - // CPU resource restriction configuration - CPU *CPU `json:"cpu,omitempty"` - // Task resource restriction configuration. - Pids *Pids `json:"pids,omitempty"` - // BlockIO restriction configuration - BlockIO *BlockIO `json:"blockIO,omitempty"` - // Hugetlb limit (in bytes) - HugepageLimits []HugepageLimit `json:"hugepageLimits,omitempty"` - // Network restriction configuration - Network *Network `json:"network,omitempty"` -} - -// Device represents the information on a Linux special device file -type Device struct { - // Path to the device. - Path string `json:"path"` - // Device type, block, char, etc. - Type rune `json:"type"` - // Major is the device's major number. - Major int64 `json:"major"` - // Minor is the device's minor number. - Minor int64 `json:"minor"` - // Cgroup permissions format, rwm. - Permissions string `json:"permissions"` - // FileMode permission bits for the device. - FileMode os.FileMode `json:"fileMode"` - // UID of the device. - UID uint32 `json:"uid"` - // Gid of the device. - GID uint32 `json:"gid"` -} - -// Seccomp represents syscall restrictions -type Seccomp struct { - DefaultAction Action `json:"defaultAction"` - Architectures []Arch `json:"architectures"` - Syscalls []*Syscall `json:"syscalls"` -} - -// Arch used for additional architectures -type Arch string - -// Additional architectures permitted to be used for system calls -// By default only the native architecture of the kernel is permitted -const ( - ArchX86 Arch = "SCMP_ARCH_X86" - ArchX86_64 Arch = "SCMP_ARCH_X86_64" - ArchX32 Arch = "SCMP_ARCH_X32" - ArchARM Arch = "SCMP_ARCH_ARM" - ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" - ArchMIPS Arch = "SCMP_ARCH_MIPS" - ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" - ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" - ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" - ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" - ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" -) - -// Action taken upon Seccomp rule match -type Action string - -// Define actions for Seccomp rules -const ( - ActKill Action = "SCMP_ACT_KILL" - ActTrap Action = "SCMP_ACT_TRAP" - ActErrno Action = "SCMP_ACT_ERRNO" - ActTrace Action = "SCMP_ACT_TRACE" - ActAllow Action = "SCMP_ACT_ALLOW" -) - -// Operator used to match syscall arguments in Seccomp -type Operator string - -// Define operators for syscall arguments in Seccomp -const ( - OpNotEqual Operator = "SCMP_CMP_NE" - OpLessThan Operator = "SCMP_CMP_LT" - OpLessEqual Operator = "SCMP_CMP_LE" - OpEqualTo Operator = "SCMP_CMP_EQ" - OpGreaterEqual Operator = "SCMP_CMP_GE" - OpGreaterThan Operator = "SCMP_CMP_GT" - OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ" -) - -// Arg used for matching specific syscall arguments in Seccomp -type Arg struct { - Index uint `json:"index"` - Value uint64 `json:"value"` - ValueTwo uint64 `json:"valueTwo"` - Op Operator `json:"op"` -} - -// Syscall is used to match a syscall in Seccomp -type Syscall struct { - Name string `json:"name"` - Action Action `json:"action"` - Args []*Arg `json:"args"` -} diff --git a/vendor/src/github.com/opencontainers/specs/style.md b/vendor/src/github.com/opencontainers/specs/style.md new file mode 100644 index 0000000..e9e86d0 --- /dev/null +++ b/vendor/src/github.com/opencontainers/specs/style.md @@ -0,0 +1,21 @@ +# Style and conventions + +## Traditionally hex settings should use JSON integers, not JSON strings + +For example, [`"classID": 1048577`][class-id] instead of `"classID": "0x100001"`. +The config JSON isn't enough of a UI to be worth jumping through string <-> integer hoops to support an 0x… form ([source][integer-over-hex]). + +## Constant names should keep redundant prefixes + +For example, `CAP_KILL` instead of `KILL` in [**`linux.capabilities`**][capabilities]). +The redundancy reduction from removing the namespacing prefix is not useful enough to be worth trimming the upstream identifier ([source][keep-prefix]). + +## Optional settings should have pointer Go types + +So we have a consistent way to identify unset values ([source][optional-pointer]). + +[capabilities]: config-linux.md#capabilities +[class-id]: config-linux.md#network +[integer-over-hex]: https://github.com/opencontainers/specs/pull/267#discussion_r48360013 +[keep-prefix]: https://github.com/opencontainers/specs/pull/159#issuecomment-138728337 +[optional-pointer]: https://github.com/opencontainers/specs/pull/233#discussion_r47829711 diff --git a/vendor/src/github.com/opencontainers/specs/version.go b/vendor/src/github.com/opencontainers/specs/version.go index c2d1e29..f236a69 100644 --- a/vendor/src/github.com/opencontainers/specs/version.go +++ b/vendor/src/github.com/opencontainers/specs/version.go @@ -6,10 +6,13 @@ const ( // VersionMajor is for an API incompatible changes VersionMajor = 0 // VersionMinor is for functionality in a backwards-compatible manner - VersionMinor = 2 + VersionMinor = 3 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 + + // VersionDev indicates development branch. Releases will be empty string. + VersionDev = "" ) // Version is the specification version that the package types support. -var Version = fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch) +var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev) diff --git a/version.go b/version.go index b741b19..f0eda81 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package containerd -const Version = "0.0.4" +const Version = "0.0.5"