github.com/demonoid81/containerd@v1.3.4/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  	f, err := openShimLog(ctx, bundle, client.AnonReconnectDialer)
    80  	if err != nil {
    81  		return nil, errors.Wrap(err, "open shim log pipe")
    82  	}
    83  	defer func() {
    84  		if err != nil {
    85  			f.Close()
    86  		}
    87  	}()
    88  	// open the log pipe and block until the writer is ready
    89  	// this helps with synchronization of the shim
    90  	// copy the shim's logs to containerd's output
    91  	go func() {
    92  		defer f.Close()
    93  		if _, err := io.Copy(os.Stderr, f); err != nil {
    94  			// When using a multi-container shim the 2nd to Nth container in the
    95  			// shim will not have a separate log pipe. Ignore the failure log
    96  			// message here when the shim connect times out.
    97  			if !os.IsNotExist(errors.Cause(err)) {
    98  				log.G(ctx).WithError(err).Error("copy shim log")
    99  			}
   100  		}
   101  	}()
   102  
   103  	client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onClose))
   104  	defer func() {
   105  		if err != nil {
   106  			client.Close()
   107  		}
   108  	}()
   109  	s := &shim{
   110  		client:  client,
   111  		task:    task.NewTaskClient(client),
   112  		bundle:  bundle,
   113  		events:  events,
   114  		rtTasks: rt,
   115  	}
   116  	ctx, cancel := timeout.WithContext(ctx, loadTimeout)
   117  	defer cancel()
   118  	if err := s.Connect(ctx); err != nil {
   119  		return nil, err
   120  	}
   121  	return s, nil
   122  }
   123  
   124  func cleanupAfterDeadShim(ctx context.Context, id, ns string, events *exchange.Exchange, binaryCall *binary) {
   125  	ctx = namespaces.WithNamespace(ctx, ns)
   126  	ctx, cancel := timeout.WithContext(ctx, cleanupTimeout)
   127  	defer cancel()
   128  
   129  	log.G(ctx).WithFields(logrus.Fields{
   130  		"id":        id,
   131  		"namespace": ns,
   132  	}).Warn("cleaning up after shim disconnected")
   133  	response, err := binaryCall.Delete(ctx)
   134  	if err != nil {
   135  		log.G(ctx).WithError(err).WithFields(logrus.Fields{
   136  			"id":        id,
   137  			"namespace": ns,
   138  		}).Warn("failed to clean up after shim disconnected")
   139  	}
   140  
   141  	var (
   142  		pid        uint32
   143  		exitStatus uint32
   144  		exitedAt   time.Time
   145  	)
   146  	if response != nil {
   147  		pid = response.Pid
   148  		exitStatus = response.Status
   149  		exitedAt = response.Timestamp
   150  	} else {
   151  		exitStatus = 255
   152  		exitedAt = time.Now()
   153  	}
   154  	events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{
   155  		ContainerID: id,
   156  		ID:          id,
   157  		Pid:         pid,
   158  		ExitStatus:  exitStatus,
   159  		ExitedAt:    exitedAt,
   160  	})
   161  
   162  	events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
   163  		ContainerID: id,
   164  		Pid:         pid,
   165  		ExitStatus:  exitStatus,
   166  		ExitedAt:    exitedAt,
   167  	})
   168  }
   169  
   170  type shim struct {
   171  	bundle  *Bundle
   172  	client  *ttrpc.Client
   173  	task    task.TaskService
   174  	taskPid int
   175  	events  *exchange.Exchange
   176  	rtTasks *runtime.TaskList
   177  }
   178  
   179  func (s *shim) Connect(ctx context.Context) error {
   180  	response, err := s.task.Connect(ctx, &task.ConnectRequest{
   181  		ID: s.ID(),
   182  	})
   183  	if err != nil {
   184  		return err
   185  	}
   186  	s.taskPid = int(response.TaskPid)
   187  	return nil
   188  }
   189  
   190  func (s *shim) Shutdown(ctx context.Context) error {
   191  	_, err := s.task.Shutdown(ctx, &task.ShutdownRequest{
   192  		ID: s.ID(),
   193  	})
   194  	if err != nil && errors.Cause(err) != ttrpc.ErrClosed {
   195  		return errdefs.FromGRPC(err)
   196  	}
   197  	return nil
   198  }
   199  
   200  func (s *shim) waitShutdown(ctx context.Context) error {
   201  	ctx, cancel := timeout.WithContext(ctx, shutdownTimeout)
   202  	defer cancel()
   203  	return s.Shutdown(ctx)
   204  }
   205  
   206  // ID of the shim/task
   207  func (s *shim) ID() string {
   208  	return s.bundle.ID
   209  }
   210  
   211  // PID of the task
   212  func (s *shim) PID() uint32 {
   213  	return uint32(s.taskPid)
   214  }
   215  
   216  func (s *shim) Namespace() string {
   217  	return s.bundle.Namespace
   218  }
   219  
   220  func (s *shim) Close() error {
   221  	return s.client.Close()
   222  }
   223  
   224  func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) {
   225  	response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{
   226  		ID: s.ID(),
   227  	})
   228  	if shimErr != nil {
   229  		shimErr = errdefs.FromGRPC(shimErr)
   230  		if !errdefs.IsNotFound(shimErr) {
   231  			return nil, shimErr
   232  		}
   233  	}
   234  	// remove self from the runtime task list
   235  	// this seems dirty but it cleans up the API across runtimes, tasks, and the service
   236  	s.rtTasks.Delete(ctx, s.ID())
   237  	if err := s.waitShutdown(ctx); err != nil {
   238  		log.G(ctx).WithError(err).Error("failed to shutdown shim")
   239  	}
   240  	s.Close()
   241  	if err := s.bundle.Delete(); err != nil {
   242  		log.G(ctx).WithError(err).Error("failed to delete bundle")
   243  	}
   244  	if shimErr != nil {
   245  		return nil, shimErr
   246  	}
   247  	return &runtime.Exit{
   248  		Status:    response.ExitStatus,
   249  		Timestamp: response.ExitedAt,
   250  		Pid:       response.Pid,
   251  	}, nil
   252  }
   253  
   254  func (s *shim) Create(ctx context.Context, opts runtime.CreateOpts) (runtime.Task, error) {
   255  	topts := opts.TaskOptions
   256  	if topts == nil {
   257  		topts = opts.RuntimeOptions
   258  	}
   259  	request := &task.CreateTaskRequest{
   260  		ID:         s.ID(),
   261  		Bundle:     s.bundle.Path,
   262  		Stdin:      opts.IO.Stdin,
   263  		Stdout:     opts.IO.Stdout,
   264  		Stderr:     opts.IO.Stderr,
   265  		Terminal:   opts.IO.Terminal,
   266  		Checkpoint: opts.Checkpoint,
   267  		Options:    topts,
   268  	}
   269  	for _, m := range opts.Rootfs {
   270  		request.Rootfs = append(request.Rootfs, &types.Mount{
   271  			Type:    m.Type,
   272  			Source:  m.Source,
   273  			Options: m.Options,
   274  		})
   275  	}
   276  	response, err := s.task.Create(ctx, request)
   277  	if err != nil {
   278  		return nil, errdefs.FromGRPC(err)
   279  	}
   280  	s.taskPid = int(response.Pid)
   281  	return s, nil
   282  }
   283  
   284  func (s *shim) Pause(ctx context.Context) error {
   285  	if _, err := s.task.Pause(ctx, &task.PauseRequest{
   286  		ID: s.ID(),
   287  	}); err != nil {
   288  		return errdefs.FromGRPC(err)
   289  	}
   290  	return nil
   291  }
   292  
   293  func (s *shim) Resume(ctx context.Context) error {
   294  	if _, err := s.task.Resume(ctx, &task.ResumeRequest{
   295  		ID: s.ID(),
   296  	}); err != nil {
   297  		return errdefs.FromGRPC(err)
   298  	}
   299  	return nil
   300  }
   301  
   302  func (s *shim) Start(ctx context.Context) error {
   303  	response, err := s.task.Start(ctx, &task.StartRequest{
   304  		ID: s.ID(),
   305  	})
   306  	if err != nil {
   307  		return errdefs.FromGRPC(err)
   308  	}
   309  	s.taskPid = int(response.Pid)
   310  	return nil
   311  }
   312  
   313  func (s *shim) Kill(ctx context.Context, signal uint32, all bool) error {
   314  	if _, err := s.task.Kill(ctx, &task.KillRequest{
   315  		ID:     s.ID(),
   316  		Signal: signal,
   317  		All:    all,
   318  	}); err != nil {
   319  		return errdefs.FromGRPC(err)
   320  	}
   321  	return nil
   322  }
   323  
   324  func (s *shim) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) {
   325  	if err := identifiers.Validate(id); err != nil {
   326  		return nil, errors.Wrapf(err, "invalid exec id %s", id)
   327  	}
   328  	request := &task.ExecProcessRequest{
   329  		ID:       s.ID(),
   330  		ExecID:   id,
   331  		Stdin:    opts.IO.Stdin,
   332  		Stdout:   opts.IO.Stdout,
   333  		Stderr:   opts.IO.Stderr,
   334  		Terminal: opts.IO.Terminal,
   335  		Spec:     opts.Spec,
   336  	}
   337  	if _, err := s.task.Exec(ctx, request); err != nil {
   338  		return nil, errdefs.FromGRPC(err)
   339  	}
   340  	return &process{
   341  		id:   id,
   342  		shim: s,
   343  	}, nil
   344  }
   345  
   346  func (s *shim) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) {
   347  	resp, err := s.task.Pids(ctx, &task.PidsRequest{
   348  		ID: s.ID(),
   349  	})
   350  	if err != nil {
   351  		return nil, errdefs.FromGRPC(err)
   352  	}
   353  	var processList []runtime.ProcessInfo
   354  	for _, p := range resp.Processes {
   355  		processList = append(processList, runtime.ProcessInfo{
   356  			Pid:  p.Pid,
   357  			Info: p.Info,
   358  		})
   359  	}
   360  	return processList, nil
   361  }
   362  
   363  func (s *shim) ResizePty(ctx context.Context, size runtime.ConsoleSize) error {
   364  	_, err := s.task.ResizePty(ctx, &task.ResizePtyRequest{
   365  		ID:     s.ID(),
   366  		Width:  size.Width,
   367  		Height: size.Height,
   368  	})
   369  	if err != nil {
   370  		return errdefs.FromGRPC(err)
   371  	}
   372  	return nil
   373  }
   374  
   375  func (s *shim) CloseIO(ctx context.Context) error {
   376  	_, err := s.task.CloseIO(ctx, &task.CloseIORequest{
   377  		ID:    s.ID(),
   378  		Stdin: true,
   379  	})
   380  	if err != nil {
   381  		return errdefs.FromGRPC(err)
   382  	}
   383  	return nil
   384  }
   385  
   386  func (s *shim) Wait(ctx context.Context) (*runtime.Exit, error) {
   387  	response, err := s.task.Wait(ctx, &task.WaitRequest{
   388  		ID: s.ID(),
   389  	})
   390  	if err != nil {
   391  		return nil, errdefs.FromGRPC(err)
   392  	}
   393  	return &runtime.Exit{
   394  		Pid:       uint32(s.taskPid),
   395  		Timestamp: response.ExitedAt,
   396  		Status:    response.ExitStatus,
   397  	}, nil
   398  }
   399  
   400  func (s *shim) Checkpoint(ctx context.Context, path string, options *ptypes.Any) error {
   401  	request := &task.CheckpointTaskRequest{
   402  		ID:      s.ID(),
   403  		Path:    path,
   404  		Options: options,
   405  	}
   406  	if _, err := s.task.Checkpoint(ctx, request); err != nil {
   407  		return errdefs.FromGRPC(err)
   408  	}
   409  	return nil
   410  }
   411  
   412  func (s *shim) Update(ctx context.Context, resources *ptypes.Any) error {
   413  	if _, err := s.task.Update(ctx, &task.UpdateTaskRequest{
   414  		ID:        s.ID(),
   415  		Resources: resources,
   416  	}); err != nil {
   417  		return errdefs.FromGRPC(err)
   418  	}
   419  	return nil
   420  }
   421  
   422  func (s *shim) Stats(ctx context.Context) (*ptypes.Any, error) {
   423  	response, err := s.task.Stats(ctx, &task.StatsRequest{
   424  		ID: s.ID(),
   425  	})
   426  	if err != nil {
   427  		return nil, errdefs.FromGRPC(err)
   428  	}
   429  	return response.Stats, nil
   430  }
   431  
   432  func (s *shim) Process(ctx context.Context, id string) (runtime.Process, error) {
   433  	return &process{
   434  		id:   id,
   435  		shim: s,
   436  	}, nil
   437  }
   438  
   439  func (s *shim) State(ctx context.Context) (runtime.State, error) {
   440  	response, err := s.task.State(ctx, &task.StateRequest{
   441  		ID: s.ID(),
   442  	})
   443  	if err != nil {
   444  		if errors.Cause(err) != ttrpc.ErrClosed {
   445  			return runtime.State{}, errdefs.FromGRPC(err)
   446  		}
   447  		return runtime.State{}, errdefs.ErrNotFound
   448  	}
   449  	var status runtime.Status
   450  	switch response.Status {
   451  	case tasktypes.StatusCreated:
   452  		status = runtime.CreatedStatus
   453  	case tasktypes.StatusRunning:
   454  		status = runtime.RunningStatus
   455  	case tasktypes.StatusStopped:
   456  		status = runtime.StoppedStatus
   457  	case tasktypes.StatusPaused:
   458  		status = runtime.PausedStatus
   459  	case tasktypes.StatusPausing:
   460  		status = runtime.PausingStatus
   461  	}
   462  	return runtime.State{
   463  		Pid:        response.Pid,
   464  		Status:     status,
   465  		Stdin:      response.Stdin,
   466  		Stdout:     response.Stdout,
   467  		Stderr:     response.Stderr,
   468  		Terminal:   response.Terminal,
   469  		ExitStatus: response.ExitStatus,
   470  		ExitedAt:   response.ExitedAt,
   471  	}, nil
   472  }