github.com/containerd/Containerd@v1.4.13/runtime/v2/shim.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package v2
    18  
    19  import (
    20  	"context"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"time"
    26  
    27  	eventstypes "github.com/containerd/containerd/api/events"
    28  	"github.com/containerd/containerd/api/types"
    29  	tasktypes "github.com/containerd/containerd/api/types/task"
    30  	"github.com/containerd/containerd/errdefs"
    31  	"github.com/containerd/containerd/events/exchange"
    32  	"github.com/containerd/containerd/identifiers"
    33  	"github.com/containerd/containerd/log"
    34  	"github.com/containerd/containerd/namespaces"
    35  	"github.com/containerd/containerd/pkg/timeout"
    36  	"github.com/containerd/containerd/runtime"
    37  	client "github.com/containerd/containerd/runtime/v2/shim"
    38  	"github.com/containerd/containerd/runtime/v2/task"
    39  	"github.com/containerd/ttrpc"
    40  	ptypes "github.com/gogo/protobuf/types"
    41  	"github.com/pkg/errors"
    42  	"github.com/sirupsen/logrus"
    43  )
    44  
    45  const (
    46  	loadTimeout     = "io.containerd.timeout.shim.load"
    47  	cleanupTimeout  = "io.containerd.timeout.shim.cleanup"
    48  	shutdownTimeout = "io.containerd.timeout.shim.shutdown"
    49  )
    50  
    51  func init() {
    52  	timeout.Set(loadTimeout, 5*time.Second)
    53  	timeout.Set(cleanupTimeout, 5*time.Second)
    54  	timeout.Set(shutdownTimeout, 3*time.Second)
    55  }
    56  
    57  func loadAddress(path string) (string, error) {
    58  	data, err := ioutil.ReadFile(path)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  	return string(data), nil
    63  }
    64  
    65  func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt *runtime.TaskList, onClose func()) (_ *shim, err error) {
    66  	address, err := loadAddress(filepath.Join(bundle.Path, "address"))
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	conn, err := client.Connect(address, client.AnonReconnectDialer)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	defer func() {
    75  		if err != nil {
    76  			conn.Close()
    77  		}
    78  	}()
    79  	shimCtx, cancelShimLog := context.WithCancel(ctx)
    80  	defer func() {
    81  		if err != nil {
    82  			cancelShimLog()
    83  		}
    84  	}()
    85  	f, err := openShimLog(shimCtx, bundle, client.AnonReconnectDialer)
    86  	if err != nil {
    87  		return nil, errors.Wrap(err, "open shim log pipe")
    88  	}
    89  	defer func() {
    90  		if err != nil {
    91  			f.Close()
    92  		}
    93  	}()
    94  	// open the log pipe and block until the writer is ready
    95  	// this helps with synchronization of the shim
    96  	// copy the shim's logs to containerd's output
    97  	go func() {
    98  		defer f.Close()
    99  		if _, err := io.Copy(os.Stderr, f); err != nil {
   100  			// When using a multi-container shim the 2nd to Nth container in the
   101  			// shim will not have a separate log pipe. Ignore the failure log
   102  			// message here when the shim connect times out.
   103  			if !errors.Is(err, os.ErrNotExist) {
   104  				log.G(ctx).WithError(err).Error("copy shim log")
   105  			}
   106  		}
   107  	}()
   108  	onCloseWithShimLog := func() {
   109  		onClose()
   110  		cancelShimLog()
   111  		f.Close()
   112  	}
   113  	client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onCloseWithShimLog))
   114  	defer func() {
   115  		if err != nil {
   116  			client.Close()
   117  		}
   118  	}()
   119  	s := &shim{
   120  		client:  client,
   121  		task:    task.NewTaskClient(client),
   122  		bundle:  bundle,
   123  		events:  events,
   124  		rtTasks: rt,
   125  	}
   126  	ctx, cancel := timeout.WithContext(ctx, loadTimeout)
   127  	defer cancel()
   128  	if err := s.Connect(ctx); err != nil {
   129  		return nil, err
   130  	}
   131  	return s, nil
   132  }
   133  
   134  func cleanupAfterDeadShim(ctx context.Context, id, ns string, events *exchange.Exchange, binaryCall *binary) {
   135  	ctx = namespaces.WithNamespace(ctx, ns)
   136  	ctx, cancel := timeout.WithContext(ctx, cleanupTimeout)
   137  	defer cancel()
   138  
   139  	log.G(ctx).WithFields(logrus.Fields{
   140  		"id":        id,
   141  		"namespace": ns,
   142  	}).Warn("cleaning up after shim disconnected")
   143  	response, err := binaryCall.Delete(ctx)
   144  	if err != nil {
   145  		log.G(ctx).WithError(err).WithFields(logrus.Fields{
   146  			"id":        id,
   147  			"namespace": ns,
   148  		}).Warn("failed to clean up after shim disconnected")
   149  	}
   150  
   151  	var (
   152  		pid        uint32
   153  		exitStatus uint32
   154  		exitedAt   time.Time
   155  	)
   156  	if response != nil {
   157  		pid = response.Pid
   158  		exitStatus = response.Status
   159  		exitedAt = response.Timestamp
   160  	} else {
   161  		exitStatus = 255
   162  		exitedAt = time.Now()
   163  	}
   164  	events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{
   165  		ContainerID: id,
   166  		ID:          id,
   167  		Pid:         pid,
   168  		ExitStatus:  exitStatus,
   169  		ExitedAt:    exitedAt,
   170  	})
   171  
   172  	events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
   173  		ContainerID: id,
   174  		Pid:         pid,
   175  		ExitStatus:  exitStatus,
   176  		ExitedAt:    exitedAt,
   177  	})
   178  }
   179  
   180  type shim struct {
   181  	bundle  *Bundle
   182  	client  *ttrpc.Client
   183  	task    task.TaskService
   184  	taskPid int
   185  	events  *exchange.Exchange
   186  	rtTasks *runtime.TaskList
   187  }
   188  
   189  func (s *shim) Connect(ctx context.Context) error {
   190  	response, err := s.task.Connect(ctx, &task.ConnectRequest{
   191  		ID: s.ID(),
   192  	})
   193  	if err != nil {
   194  		return err
   195  	}
   196  	s.taskPid = int(response.TaskPid)
   197  	return nil
   198  }
   199  
   200  func (s *shim) Shutdown(ctx context.Context) error {
   201  	_, err := s.task.Shutdown(ctx, &task.ShutdownRequest{
   202  		ID: s.ID(),
   203  	})
   204  	if err != nil && !errors.Is(err, ttrpc.ErrClosed) {
   205  		return errdefs.FromGRPC(err)
   206  	}
   207  	return nil
   208  }
   209  
   210  func (s *shim) waitShutdown(ctx context.Context) error {
   211  	ctx, cancel := timeout.WithContext(ctx, shutdownTimeout)
   212  	defer cancel()
   213  	return s.Shutdown(ctx)
   214  }
   215  
   216  // ID of the shim/task
   217  func (s *shim) ID() string {
   218  	return s.bundle.ID
   219  }
   220  
   221  // PID of the task
   222  func (s *shim) PID() uint32 {
   223  	return uint32(s.taskPid)
   224  }
   225  
   226  func (s *shim) Namespace() string {
   227  	return s.bundle.Namespace
   228  }
   229  
   230  func (s *shim) Close() error {
   231  	return s.client.Close()
   232  }
   233  
   234  func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) {
   235  	response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{
   236  		ID: s.ID(),
   237  	})
   238  	if shimErr != nil {
   239  		log.G(ctx).WithField("id", s.ID()).WithError(shimErr).Debug("failed to delete task")
   240  		if !errors.Is(shimErr, ttrpc.ErrClosed) {
   241  			shimErr = errdefs.FromGRPC(shimErr)
   242  			if !errdefs.IsNotFound(shimErr) {
   243  				return nil, shimErr
   244  			}
   245  		}
   246  	}
   247  	// remove self from the runtime task list
   248  	// this seems dirty but it cleans up the API across runtimes, tasks, and the service
   249  	s.rtTasks.Delete(ctx, s.ID())
   250  	if err := s.waitShutdown(ctx); err != nil {
   251  		log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to shutdown shim")
   252  	}
   253  	s.Close()
   254  	if err := s.bundle.Delete(); err != nil {
   255  		log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete bundle")
   256  	}
   257  	if shimErr != nil {
   258  		return nil, shimErr
   259  	}
   260  	return &runtime.Exit{
   261  		Status:    response.ExitStatus,
   262  		Timestamp: response.ExitedAt,
   263  		Pid:       response.Pid,
   264  	}, nil
   265  }
   266  
   267  func (s *shim) Create(ctx context.Context, opts runtime.CreateOpts) (runtime.Task, error) {
   268  	topts := opts.TaskOptions
   269  	if topts == nil {
   270  		topts = opts.RuntimeOptions
   271  	}
   272  	request := &task.CreateTaskRequest{
   273  		ID:         s.ID(),
   274  		Bundle:     s.bundle.Path,
   275  		Stdin:      opts.IO.Stdin,
   276  		Stdout:     opts.IO.Stdout,
   277  		Stderr:     opts.IO.Stderr,
   278  		Terminal:   opts.IO.Terminal,
   279  		Checkpoint: opts.Checkpoint,
   280  		Options:    topts,
   281  	}
   282  	for _, m := range opts.Rootfs {
   283  		request.Rootfs = append(request.Rootfs, &types.Mount{
   284  			Type:    m.Type,
   285  			Source:  m.Source,
   286  			Options: m.Options,
   287  		})
   288  	}
   289  	response, err := s.task.Create(ctx, request)
   290  	if err != nil {
   291  		return nil, errdefs.FromGRPC(err)
   292  	}
   293  	s.taskPid = int(response.Pid)
   294  	return s, nil
   295  }
   296  
   297  func (s *shim) Pause(ctx context.Context) error {
   298  	if _, err := s.task.Pause(ctx, &task.PauseRequest{
   299  		ID: s.ID(),
   300  	}); err != nil {
   301  		return errdefs.FromGRPC(err)
   302  	}
   303  	return nil
   304  }
   305  
   306  func (s *shim) Resume(ctx context.Context) error {
   307  	if _, err := s.task.Resume(ctx, &task.ResumeRequest{
   308  		ID: s.ID(),
   309  	}); err != nil {
   310  		return errdefs.FromGRPC(err)
   311  	}
   312  	return nil
   313  }
   314  
   315  func (s *shim) Start(ctx context.Context) error {
   316  	response, err := s.task.Start(ctx, &task.StartRequest{
   317  		ID: s.ID(),
   318  	})
   319  	if err != nil {
   320  		return errdefs.FromGRPC(err)
   321  	}
   322  	s.taskPid = int(response.Pid)
   323  	return nil
   324  }
   325  
   326  func (s *shim) Kill(ctx context.Context, signal uint32, all bool) error {
   327  	if _, err := s.task.Kill(ctx, &task.KillRequest{
   328  		ID:     s.ID(),
   329  		Signal: signal,
   330  		All:    all,
   331  	}); err != nil {
   332  		return errdefs.FromGRPC(err)
   333  	}
   334  	return nil
   335  }
   336  
   337  func (s *shim) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) {
   338  	if err := identifiers.Validate(id); err != nil {
   339  		return nil, errors.Wrapf(err, "invalid exec id %s", id)
   340  	}
   341  	request := &task.ExecProcessRequest{
   342  		ID:       s.ID(),
   343  		ExecID:   id,
   344  		Stdin:    opts.IO.Stdin,
   345  		Stdout:   opts.IO.Stdout,
   346  		Stderr:   opts.IO.Stderr,
   347  		Terminal: opts.IO.Terminal,
   348  		Spec:     opts.Spec,
   349  	}
   350  	if _, err := s.task.Exec(ctx, request); err != nil {
   351  		return nil, errdefs.FromGRPC(err)
   352  	}
   353  	return &process{
   354  		id:   id,
   355  		shim: s,
   356  	}, nil
   357  }
   358  
   359  func (s *shim) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) {
   360  	resp, err := s.task.Pids(ctx, &task.PidsRequest{
   361  		ID: s.ID(),
   362  	})
   363  	if err != nil {
   364  		return nil, errdefs.FromGRPC(err)
   365  	}
   366  	var processList []runtime.ProcessInfo
   367  	for _, p := range resp.Processes {
   368  		processList = append(processList, runtime.ProcessInfo{
   369  			Pid:  p.Pid,
   370  			Info: p.Info,
   371  		})
   372  	}
   373  	return processList, nil
   374  }
   375  
   376  func (s *shim) ResizePty(ctx context.Context, size runtime.ConsoleSize) error {
   377  	_, err := s.task.ResizePty(ctx, &task.ResizePtyRequest{
   378  		ID:     s.ID(),
   379  		Width:  size.Width,
   380  		Height: size.Height,
   381  	})
   382  	if err != nil {
   383  		return errdefs.FromGRPC(err)
   384  	}
   385  	return nil
   386  }
   387  
   388  func (s *shim) CloseIO(ctx context.Context) error {
   389  	_, err := s.task.CloseIO(ctx, &task.CloseIORequest{
   390  		ID:    s.ID(),
   391  		Stdin: true,
   392  	})
   393  	if err != nil {
   394  		return errdefs.FromGRPC(err)
   395  	}
   396  	return nil
   397  }
   398  
   399  func (s *shim) Wait(ctx context.Context) (*runtime.Exit, error) {
   400  	response, err := s.task.Wait(ctx, &task.WaitRequest{
   401  		ID: s.ID(),
   402  	})
   403  	if err != nil {
   404  		return nil, errdefs.FromGRPC(err)
   405  	}
   406  	return &runtime.Exit{
   407  		Pid:       uint32(s.taskPid),
   408  		Timestamp: response.ExitedAt,
   409  		Status:    response.ExitStatus,
   410  	}, nil
   411  }
   412  
   413  func (s *shim) Checkpoint(ctx context.Context, path string, options *ptypes.Any) error {
   414  	request := &task.CheckpointTaskRequest{
   415  		ID:      s.ID(),
   416  		Path:    path,
   417  		Options: options,
   418  	}
   419  	if _, err := s.task.Checkpoint(ctx, request); err != nil {
   420  		return errdefs.FromGRPC(err)
   421  	}
   422  	return nil
   423  }
   424  
   425  func (s *shim) Update(ctx context.Context, resources *ptypes.Any) error {
   426  	if _, err := s.task.Update(ctx, &task.UpdateTaskRequest{
   427  		ID:        s.ID(),
   428  		Resources: resources,
   429  	}); err != nil {
   430  		return errdefs.FromGRPC(err)
   431  	}
   432  	return nil
   433  }
   434  
   435  func (s *shim) Stats(ctx context.Context) (*ptypes.Any, error) {
   436  	response, err := s.task.Stats(ctx, &task.StatsRequest{
   437  		ID: s.ID(),
   438  	})
   439  	if err != nil {
   440  		return nil, errdefs.FromGRPC(err)
   441  	}
   442  	return response.Stats, nil
   443  }
   444  
   445  func (s *shim) Process(ctx context.Context, id string) (runtime.Process, error) {
   446  	p := &process{
   447  		id:   id,
   448  		shim: s,
   449  	}
   450  	if _, err := p.State(ctx); err != nil {
   451  		return nil, err
   452  	}
   453  	return p, nil
   454  }
   455  
   456  func (s *shim) State(ctx context.Context) (runtime.State, error) {
   457  	response, err := s.task.State(ctx, &task.StateRequest{
   458  		ID: s.ID(),
   459  	})
   460  	if err != nil {
   461  		if !errors.Is(err, ttrpc.ErrClosed) {
   462  			return runtime.State{}, errdefs.FromGRPC(err)
   463  		}
   464  		return runtime.State{}, errdefs.ErrNotFound
   465  	}
   466  	var status runtime.Status
   467  	switch response.Status {
   468  	case tasktypes.StatusCreated:
   469  		status = runtime.CreatedStatus
   470  	case tasktypes.StatusRunning:
   471  		status = runtime.RunningStatus
   472  	case tasktypes.StatusStopped:
   473  		status = runtime.StoppedStatus
   474  	case tasktypes.StatusPaused:
   475  		status = runtime.PausedStatus
   476  	case tasktypes.StatusPausing:
   477  		status = runtime.PausingStatus
   478  	}
   479  	return runtime.State{
   480  		Pid:        response.Pid,
   481  		Status:     status,
   482  		Stdin:      response.Stdin,
   483  		Stdout:     response.Stdout,
   484  		Stderr:     response.Stderr,
   485  		Terminal:   response.Terminal,
   486  		ExitStatus: response.ExitStatus,
   487  		ExitedAt:   response.ExitedAt,
   488  	}, nil
   489  }