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