...

Source file src/github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/task_wcow_podsandbox.go

Documentation: github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1

     1  //go:build windows
     2  
     3  package main
     4  
     5  import (
     6  	"context"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
    11  	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats"
    12  	"github.com/Microsoft/hcsshim/internal/cmd"
    13  	"github.com/Microsoft/hcsshim/internal/log"
    14  	"github.com/Microsoft/hcsshim/internal/oc"
    15  	"github.com/Microsoft/hcsshim/internal/shimdiag"
    16  	"github.com/Microsoft/hcsshim/internal/uvm"
    17  	eventstypes "github.com/containerd/containerd/api/events"
    18  	"github.com/containerd/containerd/errdefs"
    19  	"github.com/containerd/containerd/runtime"
    20  	"github.com/containerd/containerd/runtime/v2/task"
    21  	"github.com/containerd/typeurl"
    22  	"github.com/opencontainers/runtime-spec/specs-go"
    23  	"github.com/pkg/errors"
    24  	"go.opencensus.io/trace"
    25  )
    26  
    27  // newWcowPodSandboxTask creates a fake WCOW task with a fake WCOW `init`
    28  // process as a performance optimization rather than creating an actual
    29  // container and process since it is not needed to hold open any namespaces like
    30  // the equivalent on Linux.
    31  //
    32  // It is assumed that this is the only fake WCOW task and that this task owns
    33  // `parent`. When the fake WCOW `init` process exits via `Signal` `parent` will
    34  // be forcibly closed by this task.
    35  func newWcowPodSandboxTask(ctx context.Context, events publisher, id, bundle string, parent *uvm.UtilityVM, nsid string) shimTask {
    36  	log.G(ctx).WithField("tid", id).Debug("newWcowPodSandboxTask")
    37  
    38  	wpst := &wcowPodSandboxTask{
    39  		events: events,
    40  		id:     id,
    41  		init:   newWcowPodSandboxExec(ctx, events, id, bundle),
    42  		host:   parent,
    43  		closed: make(chan struct{}),
    44  		nsid:   nsid,
    45  	}
    46  	if parent != nil {
    47  		// We have (and own) a parent UVM. Listen for its exit and forcibly
    48  		// close this task. This is not expected but in the event of a UVM crash
    49  		// we need to handle this case.
    50  		go wpst.waitParentExit()
    51  	}
    52  	// In the normal case the `Signal` call from the caller killed this fake
    53  	// init process.
    54  	go wpst.waitInitExit()
    55  	return wpst
    56  }
    57  
    58  var _ = (shimTask)(&wcowPodSandboxTask{})
    59  
    60  // wcowPodSandboxTask is a special task type that actually holds no real
    61  // resources due to various design differences between Linux/Windows.
    62  //
    63  // For more information on why we can have this stub and in what invariant cases
    64  // it makes sense please see `wcowPodExec`.
    65  //
    66  // Note: If this is a Hypervisor Isolated WCOW sandbox then we do actually track
    67  // the lifetime of the UVM for a WCOW POD but the UVM will have no WCOW
    68  // container/exec init representing the actual POD Sandbox task.
    69  type wcowPodSandboxTask struct {
    70  	events publisher
    71  	// id is the id of this task when it is created.
    72  	//
    73  	// It MUST be treated as read only in the liftetime of the task.
    74  	id string
    75  	// init is the init process of the container.
    76  	//
    77  	// Note: the invariant `container state == init.State()` MUST be true. IE:
    78  	// if the init process exits the container as a whole and all exec's MUST
    79  	// exit.
    80  	//
    81  	// It MUST be treated as read only in the lifetime of the task.
    82  	init *wcowPodSandboxExec
    83  	// host is the hosting VM for this task if hypervisor isolated. If
    84  	// `host==nil` this is an Argon task so no UVM cleanup is required.
    85  	host *uvm.UtilityVM
    86  
    87  	// nsid is the pods network namespace ID. Normally the network for the pod would be cleaned
    88  	// up when the pod sandbox container is being torn down, but as there isn't one
    89  	// we need to do this manually. Store the network namespace ID so we can remove this on
    90  	// close.
    91  	nsid string
    92  
    93  	closed    chan struct{}
    94  	closeOnce sync.Once
    95  }
    96  
    97  func (wpst *wcowPodSandboxTask) ID() string {
    98  	return wpst.id
    99  }
   100  
   101  func (wpst *wcowPodSandboxTask) CreateExec(ctx context.Context, req *task.ExecProcessRequest, s *specs.Process) error {
   102  	return errors.Wrap(errdefs.ErrNotImplemented, "WCOW Pod task should never issue exec")
   103  }
   104  
   105  func (wpst *wcowPodSandboxTask) GetExec(eid string) (shimExec, error) {
   106  	if eid == "" {
   107  		return wpst.init, nil
   108  	}
   109  	// Cannot exec in an a WCOW sandbox container so all non-init calls fail here.
   110  	return nil, errors.Wrapf(errdefs.ErrNotFound, "exec: '%s' in task: '%s' not found", eid, wpst.id)
   111  }
   112  
   113  func (wpst *wcowPodSandboxTask) ListExecs() ([]shimExec, error) {
   114  	return []shimExec{wpst.init}, nil
   115  }
   116  
   117  func (wpst *wcowPodSandboxTask) KillExec(ctx context.Context, eid string, signal uint32, all bool) error {
   118  	e, err := wpst.GetExec(eid)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	if all && eid != "" {
   123  		return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot signal all for non-empty exec: '%s'", eid)
   124  	}
   125  	err = e.Kill(ctx, signal)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	return nil
   130  }
   131  
   132  func (wpst *wcowPodSandboxTask) DeleteExec(ctx context.Context, eid string) (int, uint32, time.Time, error) {
   133  	e, err := wpst.GetExec(eid)
   134  	if err != nil {
   135  		return 0, 0, time.Time{}, err
   136  	}
   137  	switch state := e.State(); state {
   138  	case shimExecStateCreated:
   139  		e.ForceExit(ctx, 0)
   140  	case shimExecStateRunning:
   141  		return 0, 0, time.Time{}, newExecInvalidStateError(wpst.id, eid, state, "delete")
   142  	}
   143  	status := e.Status()
   144  
   145  	// Publish the deleted event
   146  	if err := wpst.events.publishEvent(
   147  		ctx,
   148  		runtime.TaskDeleteEventTopic,
   149  		&eventstypes.TaskDelete{
   150  			ContainerID: wpst.id,
   151  			ID:          eid,
   152  			Pid:         status.Pid,
   153  			ExitStatus:  status.ExitStatus,
   154  			ExitedAt:    status.ExitedAt,
   155  		}); err != nil {
   156  		return 0, 0, time.Time{}, err
   157  	}
   158  
   159  	return int(status.Pid), status.ExitStatus, status.ExitedAt, nil
   160  }
   161  
   162  func (wpst *wcowPodSandboxTask) Pids(ctx context.Context) ([]options.ProcessDetails, error) {
   163  	return []options.ProcessDetails{
   164  		{
   165  			ProcessID: uint32(wpst.init.Pid()),
   166  			ExecID:    wpst.init.ID(),
   167  		},
   168  	}, nil
   169  }
   170  
   171  func (wpst *wcowPodSandboxTask) Wait() *task.StateResponse {
   172  	<-wpst.closed
   173  	return wpst.init.Wait()
   174  }
   175  
   176  // close safely closes the hosting UVM. Because of the specialty of this task it
   177  // is assumed that this is always the owner of `wpst.host`. Once closed and all
   178  // resources released it events the `runtime.TaskExitEventTopic` for all
   179  // upstream listeners.
   180  //
   181  // This call is idempotent and safe to call multiple times.
   182  func (wpst *wcowPodSandboxTask) close(ctx context.Context) {
   183  	wpst.closeOnce.Do(func() {
   184  		log.G(ctx).Debug("wcowPodSandboxTask::closeOnce")
   185  
   186  		if wpst.host != nil {
   187  			if err := wpst.host.TearDownNetworking(ctx, wpst.nsid); err != nil {
   188  				log.G(ctx).WithError(err).Error("failed to cleanup networking for utility VM")
   189  			}
   190  
   191  			if err := wpst.host.Close(); err != nil {
   192  				log.G(ctx).WithError(err).Error("failed host vm shutdown")
   193  			}
   194  		}
   195  		// Send the `init` exec exit notification always.
   196  		exit := wpst.init.Status()
   197  
   198  		if err := wpst.events.publishEvent(
   199  			ctx,
   200  			runtime.TaskExitEventTopic,
   201  			&eventstypes.TaskExit{
   202  				ContainerID: wpst.id,
   203  				ID:          exit.ID,
   204  				Pid:         uint32(exit.Pid),
   205  				ExitStatus:  exit.ExitStatus,
   206  				ExitedAt:    exit.ExitedAt,
   207  			}); err != nil {
   208  			log.G(ctx).WithError(err).Error("failed to publish TaskExitEventTopic")
   209  		}
   210  		close(wpst.closed)
   211  	})
   212  }
   213  
   214  func (wpst *wcowPodSandboxTask) waitInitExit() {
   215  	ctx, span := oc.StartSpan(context.Background(), "wcowPodSandboxTask::waitInitExit")
   216  	defer span.End()
   217  	span.AddAttributes(trace.StringAttribute("tid", wpst.id))
   218  
   219  	// Wait for it to exit on its own
   220  	wpst.init.Wait()
   221  
   222  	// Close the host and event the exit
   223  	wpst.close(ctx)
   224  }
   225  
   226  func (wpst *wcowPodSandboxTask) waitParentExit() {
   227  	ctx, span := oc.StartSpan(context.Background(), "wcowPodSandboxTask::waitParentExit")
   228  	defer span.End()
   229  	span.AddAttributes(trace.StringAttribute("tid", wpst.id))
   230  
   231  	werr := wpst.host.Wait()
   232  	if werr != nil {
   233  		log.G(ctx).WithError(werr).Error("parent wait failed")
   234  	}
   235  	// The UVM came down. Force transition the init task (if it wasn't
   236  	// already) to unblock any waiters since the platform wont send any
   237  	// events for this fake process.
   238  	wpst.init.ForceExit(ctx, 1)
   239  
   240  	// Close the host and event the exit.
   241  	wpst.close(ctx)
   242  }
   243  
   244  func (wpst *wcowPodSandboxTask) ExecInHost(ctx context.Context, req *shimdiag.ExecProcessRequest) (int, error) {
   245  	cmdReq := &cmd.CmdProcessRequest{
   246  		Args:     req.Args,
   247  		Workdir:  req.Workdir,
   248  		Terminal: req.Terminal,
   249  		Stdin:    req.Stdin,
   250  		Stdout:   req.Stdout,
   251  		Stderr:   req.Stderr,
   252  	}
   253  	if wpst.host == nil {
   254  		return 0, errTaskNotIsolated
   255  	}
   256  	return cmd.ExecInUvm(ctx, wpst.host, cmdReq)
   257  }
   258  
   259  func (wpst *wcowPodSandboxTask) DumpGuestStacks(ctx context.Context) string {
   260  	if wpst.host != nil {
   261  		stacks, err := wpst.host.DumpStacks(ctx)
   262  		if err != nil {
   263  			log.G(ctx).WithError(err).Warn("failed to capture guest stacks")
   264  		} else {
   265  			return stacks
   266  		}
   267  	}
   268  	return ""
   269  }
   270  
   271  func (wpst *wcowPodSandboxTask) Update(ctx context.Context, req *task.UpdateTaskRequest) error {
   272  	if wpst.host == nil {
   273  		return errTaskNotIsolated
   274  	}
   275  
   276  	resources, err := typeurl.UnmarshalAny(req.Resources)
   277  	if err != nil {
   278  		return errors.Wrapf(err, "failed to unmarshal resources for container %s update request", req.ID)
   279  	}
   280  
   281  	if err := verifyTaskUpdateResourcesType(resources); err != nil {
   282  		return err
   283  	}
   284  
   285  	return wpst.host.Update(ctx, resources, req.Annotations)
   286  }
   287  
   288  func (wpst *wcowPodSandboxTask) Share(ctx context.Context, req *shimdiag.ShareRequest) error {
   289  	if wpst.host == nil {
   290  		return errTaskNotIsolated
   291  	}
   292  	return wpst.host.Share(ctx, req.HostPath, req.UvmPath, req.ReadOnly)
   293  }
   294  
   295  func (wpst *wcowPodSandboxTask) Stats(ctx context.Context) (*stats.Statistics, error) {
   296  	stats := &stats.Statistics{}
   297  	if wpst.host == nil {
   298  		return stats, nil
   299  	}
   300  	vmStats, err := wpst.host.Stats(ctx)
   301  	if err != nil && !isStatsNotFound(err) {
   302  		return nil, err
   303  	}
   304  	stats.VM = vmStats
   305  	return stats, nil
   306  }
   307  
   308  func (wpst *wcowPodSandboxTask) ProcessorInfo(ctx context.Context) (*processorInfo, error) {
   309  	if wpst.host == nil {
   310  		return nil, errTaskNotIsolated
   311  	}
   312  	return &processorInfo{
   313  		count: wpst.host.ProcessorCount(),
   314  	}, nil
   315  }
   316  

View as plain text