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