github.com/containerd/Containerd@v1.4.13/runtime/v1/linux/task.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package linux
    20  
    21  import (
    22  	"context"
    23  	"sync"
    24  
    25  	"github.com/containerd/cgroups"
    26  	eventstypes "github.com/containerd/containerd/api/events"
    27  	"github.com/containerd/containerd/api/types/task"
    28  	"github.com/containerd/containerd/errdefs"
    29  	"github.com/containerd/containerd/events/exchange"
    30  	"github.com/containerd/containerd/identifiers"
    31  	"github.com/containerd/containerd/log"
    32  	"github.com/containerd/containerd/runtime"
    33  	"github.com/containerd/containerd/runtime/v1/shim/client"
    34  	"github.com/containerd/containerd/runtime/v1/shim/v1"
    35  	"github.com/containerd/ttrpc"
    36  	"github.com/containerd/typeurl"
    37  	"github.com/gogo/protobuf/types"
    38  	"github.com/pkg/errors"
    39  )
    40  
    41  // Task on a linux based system
    42  type Task struct {
    43  	mu        sync.Mutex
    44  	id        string
    45  	pid       int
    46  	shim      *client.Client
    47  	namespace string
    48  	cg        cgroups.Cgroup
    49  	events    *exchange.Exchange
    50  	tasks     *runtime.TaskList
    51  	bundle    *bundle
    52  }
    53  
    54  func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, list *runtime.TaskList, bundle *bundle) (*Task, error) {
    55  	var (
    56  		err error
    57  		cg  cgroups.Cgroup
    58  	)
    59  	if pid > 0 {
    60  		cg, err = cgroups.Load(cgroups.V1, cgroups.PidPath(pid))
    61  		if err != nil && err != cgroups.ErrCgroupDeleted {
    62  			return nil, err
    63  		}
    64  	}
    65  	return &Task{
    66  		id:        id,
    67  		pid:       pid,
    68  		shim:      shim,
    69  		namespace: namespace,
    70  		cg:        cg,
    71  		events:    events,
    72  		tasks:     list,
    73  		bundle:    bundle,
    74  	}, nil
    75  }
    76  
    77  // ID of the task
    78  func (t *Task) ID() string {
    79  	return t.id
    80  }
    81  
    82  // Namespace of the task
    83  func (t *Task) Namespace() string {
    84  	return t.namespace
    85  }
    86  
    87  // PID of the task
    88  func (t *Task) PID() uint32 {
    89  	return uint32(t.pid)
    90  }
    91  
    92  // Delete the task and return the exit status
    93  func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
    94  	rsp, shimErr := t.shim.Delete(ctx, empty)
    95  	if shimErr != nil {
    96  		shimErr = errdefs.FromGRPC(shimErr)
    97  		if !errdefs.IsNotFound(shimErr) {
    98  			return nil, shimErr
    99  		}
   100  	}
   101  	t.tasks.Delete(ctx, t.id)
   102  	if err := t.shim.KillShim(ctx); err != nil {
   103  		log.G(ctx).WithError(err).Error("failed to kill shim")
   104  	}
   105  	if err := t.bundle.Delete(); err != nil {
   106  		log.G(ctx).WithError(err).Error("failed to delete bundle")
   107  	}
   108  	if shimErr != nil {
   109  		return nil, shimErr
   110  	}
   111  	t.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
   112  		ContainerID: t.id,
   113  		ExitStatus:  rsp.ExitStatus,
   114  		ExitedAt:    rsp.ExitedAt,
   115  		Pid:         rsp.Pid,
   116  	})
   117  	return &runtime.Exit{
   118  		Status:    rsp.ExitStatus,
   119  		Timestamp: rsp.ExitedAt,
   120  		Pid:       rsp.Pid,
   121  	}, nil
   122  }
   123  
   124  // Start the task
   125  func (t *Task) Start(ctx context.Context) error {
   126  	t.mu.Lock()
   127  	hasCgroup := t.cg != nil
   128  	t.mu.Unlock()
   129  	r, err := t.shim.Start(ctx, &shim.StartRequest{
   130  		ID: t.id,
   131  	})
   132  	if err != nil {
   133  		return errdefs.FromGRPC(err)
   134  	}
   135  	t.pid = int(r.Pid)
   136  	if !hasCgroup {
   137  		cg, err := cgroups.Load(cgroups.V1, cgroups.PidPath(t.pid))
   138  		if err != nil && err != cgroups.ErrCgroupDeleted {
   139  			return err
   140  		}
   141  		t.mu.Lock()
   142  		if err == cgroups.ErrCgroupDeleted {
   143  			t.cg = nil
   144  		} else {
   145  			t.cg = cg
   146  		}
   147  		t.mu.Unlock()
   148  	}
   149  	t.events.Publish(ctx, runtime.TaskStartEventTopic, &eventstypes.TaskStart{
   150  		ContainerID: t.id,
   151  		Pid:         uint32(t.pid),
   152  	})
   153  	return nil
   154  }
   155  
   156  // State returns runtime information for the task
   157  func (t *Task) State(ctx context.Context) (runtime.State, error) {
   158  	response, err := t.shim.State(ctx, &shim.StateRequest{
   159  		ID: t.id,
   160  	})
   161  	if err != nil {
   162  		if !errors.Is(err, ttrpc.ErrClosed) {
   163  			return runtime.State{}, errdefs.FromGRPC(err)
   164  		}
   165  		return runtime.State{}, errdefs.ErrNotFound
   166  	}
   167  	var status runtime.Status
   168  	switch response.Status {
   169  	case task.StatusCreated:
   170  		status = runtime.CreatedStatus
   171  	case task.StatusRunning:
   172  		status = runtime.RunningStatus
   173  	case task.StatusStopped:
   174  		status = runtime.StoppedStatus
   175  	case task.StatusPaused:
   176  		status = runtime.PausedStatus
   177  	case task.StatusPausing:
   178  		status = runtime.PausingStatus
   179  	}
   180  	return runtime.State{
   181  		Pid:        response.Pid,
   182  		Status:     status,
   183  		Stdin:      response.Stdin,
   184  		Stdout:     response.Stdout,
   185  		Stderr:     response.Stderr,
   186  		Terminal:   response.Terminal,
   187  		ExitStatus: response.ExitStatus,
   188  		ExitedAt:   response.ExitedAt,
   189  	}, nil
   190  }
   191  
   192  // Pause the task and all processes
   193  func (t *Task) Pause(ctx context.Context) error {
   194  	if _, err := t.shim.Pause(ctx, empty); err != nil {
   195  		return errdefs.FromGRPC(err)
   196  	}
   197  	t.events.Publish(ctx, runtime.TaskPausedEventTopic, &eventstypes.TaskPaused{
   198  		ContainerID: t.id,
   199  	})
   200  	return nil
   201  }
   202  
   203  // Resume the task and all processes
   204  func (t *Task) Resume(ctx context.Context) error {
   205  	if _, err := t.shim.Resume(ctx, empty); err != nil {
   206  		return errdefs.FromGRPC(err)
   207  	}
   208  	t.events.Publish(ctx, runtime.TaskResumedEventTopic, &eventstypes.TaskResumed{
   209  		ContainerID: t.id,
   210  	})
   211  	return nil
   212  }
   213  
   214  // Kill the task using the provided signal
   215  //
   216  // Optionally send the signal to all processes that are a child of the task
   217  func (t *Task) Kill(ctx context.Context, signal uint32, all bool) error {
   218  	if _, err := t.shim.Kill(ctx, &shim.KillRequest{
   219  		ID:     t.id,
   220  		Signal: signal,
   221  		All:    all,
   222  	}); err != nil {
   223  		return errdefs.FromGRPC(err)
   224  	}
   225  	return nil
   226  }
   227  
   228  // Exec creates a new process inside the task
   229  func (t *Task) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) {
   230  	if err := identifiers.Validate(id); err != nil {
   231  		return nil, errors.Wrapf(err, "invalid exec id")
   232  	}
   233  	request := &shim.ExecProcessRequest{
   234  		ID:       id,
   235  		Stdin:    opts.IO.Stdin,
   236  		Stdout:   opts.IO.Stdout,
   237  		Stderr:   opts.IO.Stderr,
   238  		Terminal: opts.IO.Terminal,
   239  		Spec:     opts.Spec,
   240  	}
   241  	if _, err := t.shim.Exec(ctx, request); err != nil {
   242  		return nil, errdefs.FromGRPC(err)
   243  	}
   244  	return &Process{
   245  		id: id,
   246  		t:  t,
   247  	}, nil
   248  }
   249  
   250  // Pids returns all system level process ids running inside the task
   251  func (t *Task) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) {
   252  	resp, err := t.shim.ListPids(ctx, &shim.ListPidsRequest{
   253  		ID: t.id,
   254  	})
   255  	if err != nil {
   256  		return nil, errdefs.FromGRPC(err)
   257  	}
   258  	var processList []runtime.ProcessInfo
   259  	for _, p := range resp.Processes {
   260  		processList = append(processList, runtime.ProcessInfo{
   261  			Pid:  p.Pid,
   262  			Info: p.Info,
   263  		})
   264  	}
   265  	return processList, nil
   266  }
   267  
   268  // ResizePty changes the side of the task's PTY to the provided width and height
   269  func (t *Task) ResizePty(ctx context.Context, size runtime.ConsoleSize) error {
   270  	_, err := t.shim.ResizePty(ctx, &shim.ResizePtyRequest{
   271  		ID:     t.id,
   272  		Width:  size.Width,
   273  		Height: size.Height,
   274  	})
   275  	if err != nil {
   276  		err = errdefs.FromGRPC(err)
   277  	}
   278  	return err
   279  }
   280  
   281  // CloseIO closes the provided IO on the task
   282  func (t *Task) CloseIO(ctx context.Context) error {
   283  	_, err := t.shim.CloseIO(ctx, &shim.CloseIORequest{
   284  		ID:    t.id,
   285  		Stdin: true,
   286  	})
   287  	if err != nil {
   288  		err = errdefs.FromGRPC(err)
   289  	}
   290  	return err
   291  }
   292  
   293  // Checkpoint creates a system level dump of the task and process information that can be later restored
   294  func (t *Task) Checkpoint(ctx context.Context, path string, options *types.Any) error {
   295  	r := &shim.CheckpointTaskRequest{
   296  		Path:    path,
   297  		Options: options,
   298  	}
   299  	if _, err := t.shim.Checkpoint(ctx, r); err != nil {
   300  		return errdefs.FromGRPC(err)
   301  	}
   302  	t.events.Publish(ctx, runtime.TaskCheckpointedEventTopic, &eventstypes.TaskCheckpointed{
   303  		ContainerID: t.id,
   304  	})
   305  	return nil
   306  }
   307  
   308  // Update changes runtime information of a running task
   309  func (t *Task) Update(ctx context.Context, resources *types.Any) error {
   310  	if _, err := t.shim.Update(ctx, &shim.UpdateTaskRequest{
   311  		Resources: resources,
   312  	}); err != nil {
   313  		return errdefs.FromGRPC(err)
   314  	}
   315  	return nil
   316  }
   317  
   318  // Process returns a specific process inside the task by the process id
   319  func (t *Task) Process(ctx context.Context, id string) (runtime.Process, error) {
   320  	p := &Process{
   321  		id: id,
   322  		t:  t,
   323  	}
   324  	if _, err := p.State(ctx); err != nil {
   325  		return nil, err
   326  	}
   327  	return p, nil
   328  }
   329  
   330  // Stats returns runtime specific system level metric information for the task
   331  func (t *Task) Stats(ctx context.Context) (*types.Any, error) {
   332  	t.mu.Lock()
   333  	defer t.mu.Unlock()
   334  	if t.cg == nil {
   335  		return nil, errors.Wrap(errdefs.ErrNotFound, "cgroup does not exist")
   336  	}
   337  	stats, err := t.cg.Stat(cgroups.IgnoreNotExist)
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  	return typeurl.MarshalAny(stats)
   342  }
   343  
   344  // Cgroup returns the underlying cgroup for a linux task
   345  func (t *Task) Cgroup() (cgroups.Cgroup, error) {
   346  	t.mu.Lock()
   347  	defer t.mu.Unlock()
   348  	if t.cg == nil {
   349  		return nil, errors.Wrap(errdefs.ErrNotFound, "cgroup does not exist")
   350  	}
   351  	return t.cg, nil
   352  }
   353  
   354  // Wait for the task to exit returning the status and timestamp
   355  func (t *Task) Wait(ctx context.Context) (*runtime.Exit, error) {
   356  	r, err := t.shim.Wait(ctx, &shim.WaitRequest{
   357  		ID: t.id,
   358  	})
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	return &runtime.Exit{
   363  		Timestamp: r.ExitedAt,
   364  		Status:    r.ExitStatus,
   365  	}, nil
   366  }