package server import ( "bufio" "errors" "fmt" "os" "strconv" "strings" "github.com/docker/containerd/api/grpc/types" "github.com/docker/containerd/runtime" "github.com/docker/containerd/specs" "github.com/docker/containerd/supervisor" "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/system" ocs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) func createContainerConfigCheckpoint(e *supervisor.StartTask, c *types.CreateContainerRequest) { if c.Checkpoint != "" { e.Checkpoint = &runtime.Checkpoint{ Name: c.Checkpoint, } } } func (s *apiServer) CreateCheckpoint(ctx context.Context, r *types.CreateCheckpointRequest) (*types.CreateCheckpointResponse, error) { e := &supervisor.CreateCheckpointTask{} e.ID = r.Id e.Checkpoint = &runtime.Checkpoint{ Name: r.Checkpoint.Name, Exit: r.Checkpoint.Exit, Tcp: r.Checkpoint.Tcp, UnixSockets: r.Checkpoint.UnixSockets, Shell: r.Checkpoint.Shell, } s.sv.SendTask(e) if err := <-e.ErrorCh(); err != nil { return nil, err } return &types.CreateCheckpointResponse{}, nil } func (s *apiServer) DeleteCheckpoint(ctx context.Context, r *types.DeleteCheckpointRequest) (*types.DeleteCheckpointResponse, error) { if r.Name == "" { return nil, errors.New("checkpoint name cannot be empty") } e := &supervisor.DeleteCheckpointTask{} e.ID = r.Id e.Checkpoint = &runtime.Checkpoint{ Name: r.Name, } s.sv.SendTask(e) if err := <-e.ErrorCh(); err != nil { return nil, err } return &types.DeleteCheckpointResponse{}, nil } func (s *apiServer) ListCheckpoint(ctx context.Context, r *types.ListCheckpointRequest) (*types.ListCheckpointResponse, error) { e := &supervisor.GetContainersTask{} s.sv.SendTask(e) if err := <-e.ErrorCh(); err != nil { return nil, err } var container runtime.Container for _, c := range e.Containers { if c.ID() == r.Id { container = c break } } if container == nil { return nil, grpc.Errorf(codes.NotFound, "no such containers") } var out []*types.Checkpoint checkpoints, err := container.Checkpoints() if err != nil { return nil, err } for _, c := range checkpoints { out = append(out, &types.Checkpoint{ Name: c.Name, Tcp: c.Tcp, Shell: c.Shell, UnixSockets: c.UnixSockets, // TODO: figure out timestamp //Timestamp: c.Timestamp, }) } return &types.ListCheckpointResponse{Checkpoints: out}, nil } func 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 systemUsage, _ := getSystemCPUUsage() 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, }, SystemUsage: systemUsage, } 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, Limit: memSt.Usage.Limit, }, SwapUsage: &types.MemoryData{ Usage: memSt.SwapUsage.Usage, MaxUsage: memSt.SwapUsage.MaxUsage, Failcnt: memSt.SwapUsage.Failcnt, Limit: memSt.SwapUsage.Limit, }, KernelUsage: &types.MemoryData{ Usage: memSt.KernelUsage.Usage, MaxUsage: memSt.KernelUsage.MaxUsage, Failcnt: memSt.KernelUsage.Failcnt, Limit: memSt.KernelUsage.Limit, }, } 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, } } pbSt.CgroupStats.PidsStats = &types.PidsStats{ Current: lcSt.CgroupStats.PidsStats.Current, Limit: lcSt.CgroupStats.PidsStats.Limit, } 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 } const nanoSecondsPerSecond = 1e9 var clockTicksPerSecond = uint64(system.GetClockTicks()) // 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 getSystemCPUUsage() (uint64, error) { var line string f, err := os.Open("/proc/stat") if err != nil { return 0, err } bufReader := bufio.NewReaderSize(nil, 128) defer func() { bufReader.Reset(nil) f.Close() }() bufReader.Reset(f) err = nil for err == nil { line, err = 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) / clockTicksPerSecond, nil } } return 0, fmt.Errorf("bad stats format") } func (s *apiServer) Stats(ctx context.Context, r *types.StatsRequest) (*types.StatsResponse, error) { e := &supervisor.StatsTask{} e.ID = r.Id e.Stat = make(chan *runtime.Stat, 1) s.sv.SendTask(e) if err := <-e.ErrorCh(); err != nil { return nil, err } stats := <-e.Stat t := convertToPb(stats) return t, nil } func setUserFieldsInProcess(p *types.Process, oldProc specs.ProcessSpec) { p.User = &types.User{ Uid: oldProc.User.UID, Gid: oldProc.User.GID, AdditionalGids: oldProc.User.AdditionalGids, } p.Capabilities = oldProc.Capabilities p.ApparmorProfile = oldProc.ApparmorProfile p.SelinuxLabel = oldProc.SelinuxLabel p.NoNewPrivileges = oldProc.NoNewPrivileges for _, rl := range oldProc.Rlimits { p.Rlimits = append(p.Rlimits, &types.Rlimit{ Type: rl.Type, Soft: rl.Soft, Hard: rl.Hard, }) } } func setPlatformRuntimeProcessSpecUserFields(r *types.AddProcessRequest, process *specs.ProcessSpec) { process.User = ocs.User{ UID: r.User.Uid, GID: r.User.Gid, AdditionalGids: r.User.AdditionalGids, } process.Capabilities = r.Capabilities process.ApparmorProfile = r.ApparmorProfile process.SelinuxLabel = r.SelinuxLabel process.NoNewPrivileges = r.NoNewPrivileges for _, rl := range r.Rlimits { process.Rlimits = append(process.Rlimits, ocs.Rlimit{ Type: rl.Type, Soft: rl.Soft, Hard: rl.Hard, }) } }