With this change, we integrate all the plugin changes into the
introspection service.
All plugins can be listed with the following command:
```console
$ ctr plugins
TYPE                            ID             PLATFORM       STATUS
io.containerd.content.v1        content        -              ok
io.containerd.metadata.v1       bolt           -              ok
io.containerd.differ.v1         walking        linux/amd64    ok
io.containerd.grpc.v1           containers     -              ok
io.containerd.grpc.v1           content        -              ok
io.containerd.grpc.v1           diff           -              ok
io.containerd.grpc.v1           events         -              ok
io.containerd.grpc.v1           healthcheck    -              ok
io.containerd.grpc.v1           images         -              ok
io.containerd.grpc.v1           namespaces     -              ok
io.containerd.snapshotter.v1    btrfs          linux/amd64    error
io.containerd.snapshotter.v1    overlayfs      linux/amd64    ok
io.containerd.grpc.v1           snapshots      -              ok
io.containerd.monitor.v1        cgroups        linux/amd64    ok
io.containerd.runtime.v1        linux          linux/amd64    ok
io.containerd.grpc.v1           tasks          -              ok
io.containerd.grpc.v1           version        -              ok
```
There are few things to note about this output. The first is that it is
printed in the order in which plugins are initialized. This useful for
debugging plugin initialization problems. Also note that even though the
introspection GPRC api is a itself a plugin, it is not listed. This is
because the plugin takes a snapshot of the initialization state at the
end of the plugin init process. This allows us to see errors from each
plugin, as they happen. If it is required to introspect the existence of
the introspection service, we can make modifications to include it in
the future.
The last thing to note is that the btrfs plugin is in an error state.
This is a common state for containerd because even though we load the
plugin, most installations aren't on top of btrfs and the plugin cannot
be used. We can actually view this error using the detailed view with a
filter:
```console
$ ctr plugins --detailed id==btrfs
Type:          io.containerd.snapshotter.v1
ID:            btrfs
Platforms:     linux/amd64
Exports:
               root      /var/lib/containerd/io.containerd.snapshotter.v1.btrfs
Error:
               Code:        Unknown
               Message:     path /var/lib/containerd/io.containerd.snapshotter.v1.btrfs must be a btrfs filesystem to be used with the btrfs snapshotter
```
Along with several other values, this is a valuable tool for evaluating the
state of components in containerd.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
		
	
		
			
				
	
	
		
			620 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			620 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package tasks
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/boltdb/bolt"
 | 
						|
	api "github.com/containerd/containerd/api/services/tasks/v1"
 | 
						|
	"github.com/containerd/containerd/api/types"
 | 
						|
	"github.com/containerd/containerd/api/types/task"
 | 
						|
	"github.com/containerd/containerd/archive"
 | 
						|
	"github.com/containerd/containerd/containers"
 | 
						|
	"github.com/containerd/containerd/content"
 | 
						|
	"github.com/containerd/containerd/errdefs"
 | 
						|
	"github.com/containerd/containerd/events"
 | 
						|
	"github.com/containerd/containerd/filters"
 | 
						|
	"github.com/containerd/containerd/images"
 | 
						|
	"github.com/containerd/containerd/log"
 | 
						|
	"github.com/containerd/containerd/metadata"
 | 
						|
	"github.com/containerd/containerd/mount"
 | 
						|
	"github.com/containerd/containerd/plugin"
 | 
						|
	"github.com/containerd/containerd/runtime"
 | 
						|
	"github.com/containerd/typeurl"
 | 
						|
	google_protobuf "github.com/golang/protobuf/ptypes/empty"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"golang.org/x/net/context"
 | 
						|
	"google.golang.org/grpc"
 | 
						|
	"google.golang.org/grpc/codes"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	_     = (api.TasksServer)(&Service{})
 | 
						|
	empty = &google_protobuf.Empty{}
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	plugin.Register(&plugin.Registration{
 | 
						|
		Type: plugin.GRPCPlugin,
 | 
						|
		ID:   "tasks",
 | 
						|
		Requires: []plugin.Type{
 | 
						|
			plugin.RuntimePlugin,
 | 
						|
			plugin.MetadataPlugin,
 | 
						|
		},
 | 
						|
		InitFn: New,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func New(ic *plugin.InitContext) (interface{}, error) {
 | 
						|
	rt, err := ic.GetByType(plugin.RuntimePlugin)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	m, err := ic.Get(plugin.MetadataPlugin)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	cs := m.(*metadata.DB).ContentStore()
 | 
						|
	runtimes := make(map[string]runtime.Runtime)
 | 
						|
	for _, rr := range rt {
 | 
						|
		ri, err := rr.Instance()
 | 
						|
		if err != nil {
 | 
						|
			log.G(ic.Context).WithError(err).Warn("could not load runtime instance due to initialization error")
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		r := ri.(runtime.Runtime)
 | 
						|
		runtimes[r.ID()] = r
 | 
						|
	}
 | 
						|
 | 
						|
	if len(runtimes) == 0 {
 | 
						|
		return nil, errors.New("no runtimes available to create task service")
 | 
						|
	}
 | 
						|
	return &Service{
 | 
						|
		runtimes:  runtimes,
 | 
						|
		db:        m.(*metadata.DB),
 | 
						|
		store:     cs,
 | 
						|
		publisher: ic.Events,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
type Service struct {
 | 
						|
	runtimes  map[string]runtime.Runtime
 | 
						|
	db        *metadata.DB
 | 
						|
	store     content.Store
 | 
						|
	publisher events.Publisher
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Register(server *grpc.Server) error {
 | 
						|
	api.RegisterTasksServer(server, s)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Create(ctx context.Context, r *api.CreateTaskRequest) (*api.CreateTaskResponse, error) {
 | 
						|
	var (
 | 
						|
		checkpointPath string
 | 
						|
		err            error
 | 
						|
	)
 | 
						|
	if r.Checkpoint != nil {
 | 
						|
		checkpointPath, err = ioutil.TempDir("", "ctrd-checkpoint")
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		if r.Checkpoint.MediaType != images.MediaTypeContainerd1Checkpoint {
 | 
						|
			return nil, fmt.Errorf("unsupported checkpoint type %q", r.Checkpoint.MediaType)
 | 
						|
		}
 | 
						|
		reader, err := s.store.ReaderAt(ctx, r.Checkpoint.Digest)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		_, err = archive.Apply(ctx, checkpointPath, content.NewReader(reader))
 | 
						|
		reader.Close()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	container, err := s.getContainer(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	opts := runtime.CreateOpts{
 | 
						|
		Spec: container.Spec,
 | 
						|
		IO: runtime.IO{
 | 
						|
			Stdin:    r.Stdin,
 | 
						|
			Stdout:   r.Stdout,
 | 
						|
			Stderr:   r.Stderr,
 | 
						|
			Terminal: r.Terminal,
 | 
						|
		},
 | 
						|
		Checkpoint: checkpointPath,
 | 
						|
		Options:    r.Options,
 | 
						|
	}
 | 
						|
	for _, m := range r.Rootfs {
 | 
						|
		opts.Rootfs = append(opts.Rootfs, mount.Mount{
 | 
						|
			Type:    m.Type,
 | 
						|
			Source:  m.Source,
 | 
						|
			Options: m.Options,
 | 
						|
		})
 | 
						|
	}
 | 
						|
	runtime, err := s.getRuntime(container.Runtime.Name)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	c, err := runtime.Create(ctx, r.ContainerID, opts)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	state, err := c.State(ctx)
 | 
						|
	if err != nil {
 | 
						|
		log.G(ctx).Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &api.CreateTaskResponse{
 | 
						|
		ContainerID: r.ContainerID,
 | 
						|
		Pid:         state.Pid,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Start(ctx context.Context, r *api.StartRequest) (*api.StartResponse, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p := runtime.Process(t)
 | 
						|
	if r.ExecID != "" {
 | 
						|
		if p, err = t.Process(ctx, r.ExecID); err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := p.Start(ctx); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	state, err := p.State(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return &api.StartResponse{
 | 
						|
		Pid: state.Pid,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Delete(ctx context.Context, r *api.DeleteTaskRequest) (*api.DeleteResponse, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	runtime, err := s.getRuntime(t.Info().Runtime)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	exit, err := runtime.Delete(ctx, t)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return &api.DeleteResponse{
 | 
						|
		ExitStatus: exit.Status,
 | 
						|
		ExitedAt:   exit.Timestamp,
 | 
						|
		Pid:        exit.Pid,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) DeleteProcess(ctx context.Context, r *api.DeleteProcessRequest) (*api.DeleteResponse, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	exit, err := t.DeleteProcess(ctx, r.ExecID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return &api.DeleteResponse{
 | 
						|
		ID:         r.ExecID,
 | 
						|
		ExitStatus: exit.Status,
 | 
						|
		ExitedAt:   exit.Timestamp,
 | 
						|
		Pid:        exit.Pid,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func processFromContainerd(ctx context.Context, p runtime.Process) (*task.Process, error) {
 | 
						|
	state, err := p.State(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	var status task.Status
 | 
						|
	switch state.Status {
 | 
						|
	case runtime.CreatedStatus:
 | 
						|
		status = task.StatusCreated
 | 
						|
	case runtime.RunningStatus:
 | 
						|
		status = task.StatusRunning
 | 
						|
	case runtime.StoppedStatus:
 | 
						|
		status = task.StatusStopped
 | 
						|
	case runtime.PausedStatus:
 | 
						|
		status = task.StatusPaused
 | 
						|
	case runtime.PausingStatus:
 | 
						|
		status = task.StatusPausing
 | 
						|
	default:
 | 
						|
		log.G(ctx).WithField("status", state.Status).Warn("unknown status")
 | 
						|
	}
 | 
						|
	return &task.Process{
 | 
						|
		ID:         p.ID(),
 | 
						|
		Pid:        state.Pid,
 | 
						|
		Status:     status,
 | 
						|
		Stdin:      state.Stdin,
 | 
						|
		Stdout:     state.Stdout,
 | 
						|
		Stderr:     state.Stderr,
 | 
						|
		Terminal:   state.Terminal,
 | 
						|
		ExitStatus: state.ExitStatus,
 | 
						|
		ExitedAt:   state.ExitedAt,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Get(ctx context.Context, r *api.GetRequest) (*api.GetResponse, error) {
 | 
						|
	task, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p := runtime.Process(task)
 | 
						|
	if r.ExecID != "" {
 | 
						|
		if p, err = task.Process(ctx, r.ExecID); err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	t, err := processFromContainerd(ctx, p)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return &api.GetResponse{
 | 
						|
		Process: t,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) List(ctx context.Context, r *api.ListTasksRequest) (*api.ListTasksResponse, error) {
 | 
						|
	resp := &api.ListTasksResponse{}
 | 
						|
	for _, r := range s.runtimes {
 | 
						|
		tasks, err := r.Tasks(ctx)
 | 
						|
		if err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
		addTasks(ctx, resp, tasks)
 | 
						|
	}
 | 
						|
	return resp, nil
 | 
						|
}
 | 
						|
 | 
						|
func addTasks(ctx context.Context, r *api.ListTasksResponse, tasks []runtime.Task) {
 | 
						|
	for _, t := range tasks {
 | 
						|
		tt, err := processFromContainerd(ctx, t)
 | 
						|
		if err != nil {
 | 
						|
			log.G(ctx).WithError(err).WithField("id", t.ID()).Error("converting task to protobuf")
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		r.Tasks = append(r.Tasks, tt)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Pause(ctx context.Context, r *api.PauseTaskRequest) (*google_protobuf.Empty, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	err = t.Pause(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Resume(ctx context.Context, r *api.ResumeTaskRequest) (*google_protobuf.Empty, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	err = t.Resume(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Kill(ctx context.Context, r *api.KillRequest) (*google_protobuf.Empty, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p := runtime.Process(t)
 | 
						|
	if r.ExecID != "" {
 | 
						|
		if p, err = t.Process(ctx, r.ExecID); err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := p.Kill(ctx, r.Signal, r.All); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) ListPids(ctx context.Context, r *api.ListPidsRequest) (*api.ListPidsResponse, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	processList, err := t.Pids(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	var processes []*task.ProcessInfo
 | 
						|
	for _, p := range processList {
 | 
						|
		processInfo := task.ProcessInfo{
 | 
						|
			Pid: p.Pid,
 | 
						|
		}
 | 
						|
		if p.Info != nil {
 | 
						|
			a, err := typeurl.MarshalAny(p.Info)
 | 
						|
			if err != nil {
 | 
						|
				return nil, errors.Wrapf(err, "failed to marshal process %d info", p.Pid)
 | 
						|
			}
 | 
						|
			processInfo.Info = a
 | 
						|
		}
 | 
						|
		processes = append(processes, &processInfo)
 | 
						|
	}
 | 
						|
	return &api.ListPidsResponse{
 | 
						|
		Processes: processes,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Exec(ctx context.Context, r *api.ExecProcessRequest) (*google_protobuf.Empty, error) {
 | 
						|
	if r.ExecID == "" {
 | 
						|
		return nil, grpc.Errorf(codes.InvalidArgument, "exec id cannot be empty")
 | 
						|
	}
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if _, err := t.Exec(ctx, r.ExecID, runtime.ExecOpts{
 | 
						|
		Spec: r.Spec,
 | 
						|
		IO: runtime.IO{
 | 
						|
			Stdin:    r.Stdin,
 | 
						|
			Stdout:   r.Stdout,
 | 
						|
			Stderr:   r.Stderr,
 | 
						|
			Terminal: r.Terminal,
 | 
						|
		},
 | 
						|
	}); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) ResizePty(ctx context.Context, r *api.ResizePtyRequest) (*google_protobuf.Empty, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p := runtime.Process(t)
 | 
						|
	if r.ExecID != "" {
 | 
						|
		if p, err = t.Process(ctx, r.ExecID); err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := p.ResizePty(ctx, runtime.ConsoleSize{
 | 
						|
		Width:  r.Width,
 | 
						|
		Height: r.Height,
 | 
						|
	}); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) CloseIO(ctx context.Context, r *api.CloseIORequest) (*google_protobuf.Empty, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p := runtime.Process(t)
 | 
						|
	if r.ExecID != "" {
 | 
						|
		if p, err = t.Process(ctx, r.ExecID); err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if r.Stdin {
 | 
						|
		if err := p.CloseIO(ctx); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Checkpoint(ctx context.Context, r *api.CheckpointTaskRequest) (*api.CheckpointTaskResponse, error) {
 | 
						|
	container, err := s.getContainer(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	t, err := s.getTaskFromContainer(ctx, container)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	image, err := ioutil.TempDir("", "ctd-checkpoint")
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(image)
 | 
						|
	if err := t.Checkpoint(ctx, image, r.Options); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	// write checkpoint to the content store
 | 
						|
	tar := archive.Diff(ctx, "", image)
 | 
						|
	cp, err := s.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, image, tar)
 | 
						|
	// close tar first after write
 | 
						|
	if err := tar.Close(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// write the config to the content store
 | 
						|
	data, err := container.Spec.Marshal()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	spec := bytes.NewReader(data)
 | 
						|
	specD, err := s.writeContent(ctx, images.MediaTypeContainerd1CheckpointConfig, filepath.Join(image, "spec"), spec)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return &api.CheckpointTaskResponse{
 | 
						|
		Descriptors: []*types.Descriptor{
 | 
						|
			cp,
 | 
						|
			specD,
 | 
						|
		},
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Update(ctx context.Context, r *api.UpdateTaskRequest) (*google_protobuf.Empty, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err := t.Update(ctx, r.Resources); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return empty, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Metrics(ctx context.Context, r *api.MetricsRequest) (*api.MetricsResponse, error) {
 | 
						|
	filter, err := filters.ParseAll(r.Filters...)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var resp api.MetricsResponse
 | 
						|
	for _, r := range s.runtimes {
 | 
						|
		tasks, err := r.Tasks(ctx)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		getTasksMetrics(ctx, filter, tasks, &resp)
 | 
						|
	}
 | 
						|
	return &resp, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) Wait(ctx context.Context, r *api.WaitRequest) (*api.WaitResponse, error) {
 | 
						|
	t, err := s.getTask(ctx, r.ContainerID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	p := runtime.Process(t)
 | 
						|
	if r.ExecID != "" {
 | 
						|
		if p, err = t.Process(ctx, r.ExecID); err != nil {
 | 
						|
			return nil, errdefs.ToGRPC(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	exit, err := p.Wait(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return &api.WaitResponse{
 | 
						|
		ExitStatus: exit.Status,
 | 
						|
		ExitedAt:   exit.Timestamp,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func getTasksMetrics(ctx context.Context, filter filters.Filter, tasks []runtime.Task, r *api.MetricsResponse) {
 | 
						|
	for _, tk := range tasks {
 | 
						|
		if !filter.Match(filters.AdapterFunc(func(fieldpath []string) (string, bool) {
 | 
						|
			t := tk
 | 
						|
			switch fieldpath[0] {
 | 
						|
			case "id":
 | 
						|
				return t.ID(), true
 | 
						|
			case "namespace":
 | 
						|
				return t.Info().Namespace, true
 | 
						|
			case "runtime":
 | 
						|
				return t.Info().Runtime, true
 | 
						|
			}
 | 
						|
			return "", false
 | 
						|
		})) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		collected := time.Now()
 | 
						|
		metrics, err := tk.Metrics(ctx)
 | 
						|
		if err != nil {
 | 
						|
			if !errdefs.IsNotFound(err) {
 | 
						|
				log.G(ctx).WithError(err).Errorf("collecting metrics for %s", tk.ID())
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		data, err := typeurl.MarshalAny(metrics)
 | 
						|
		if err != nil {
 | 
						|
			log.G(ctx).WithError(err).Errorf("marshal metrics for %s", tk.ID())
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		r.Metrics = append(r.Metrics, &types.Metric{
 | 
						|
			ID:        tk.ID(),
 | 
						|
			Timestamp: collected,
 | 
						|
			Data:      data,
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
 | 
						|
	writer, err := s.store.Writer(ctx, ref, 0, "")
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer writer.Close()
 | 
						|
	size, err := io.Copy(writer, r)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err := writer.Commit(ctx, 0, ""); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return &types.Descriptor{
 | 
						|
		MediaType: mediaType,
 | 
						|
		Digest:    writer.Digest(),
 | 
						|
		Size_:     size,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) getContainer(ctx context.Context, id string) (*containers.Container, error) {
 | 
						|
	var container containers.Container
 | 
						|
	if err := s.db.View(func(tx *bolt.Tx) error {
 | 
						|
		store := metadata.NewContainerStore(tx)
 | 
						|
		var err error
 | 
						|
		container, err = store.Get(ctx, id)
 | 
						|
		return err
 | 
						|
	}); err != nil {
 | 
						|
		return nil, errdefs.ToGRPC(err)
 | 
						|
	}
 | 
						|
	return &container, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) getTask(ctx context.Context, id string) (runtime.Task, error) {
 | 
						|
	container, err := s.getContainer(ctx, id)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return s.getTaskFromContainer(ctx, container)
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) getTaskFromContainer(ctx context.Context, container *containers.Container) (runtime.Task, error) {
 | 
						|
	runtime, err := s.getRuntime(container.Runtime.Name)
 | 
						|
	if err != nil {
 | 
						|
		return nil, errdefs.ToGRPCf(err, "runtime for task %s", container.Runtime.Name)
 | 
						|
	}
 | 
						|
	t, err := runtime.Get(ctx, container.ID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, grpc.Errorf(codes.NotFound, "task %v not found", container.ID)
 | 
						|
	}
 | 
						|
	return t, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Service) getRuntime(name string) (runtime.Runtime, error) {
 | 
						|
	runtime, ok := s.runtimes[name]
 | 
						|
	if !ok {
 | 
						|
		return nil, grpc.Errorf(codes.NotFound, "unknown runtime %q", name)
 | 
						|
	}
 | 
						|
	return runtime, nil
 | 
						|
}
 |