1package main
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"path/filepath"
8	"sync"
9	"time"
10
11	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
12	runhcsopts "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
13	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats"
14	"github.com/Microsoft/hcsshim/internal/cmd"
15	"github.com/Microsoft/hcsshim/internal/cow"
16	"github.com/Microsoft/hcsshim/internal/hcs"
17	"github.com/Microsoft/hcsshim/internal/hcsoci"
18	"github.com/Microsoft/hcsshim/internal/log"
19	"github.com/Microsoft/hcsshim/internal/oci"
20	"github.com/Microsoft/hcsshim/internal/resources"
21	"github.com/Microsoft/hcsshim/internal/schema1"
22	hcsschema "github.com/Microsoft/hcsshim/internal/schema2"
23	"github.com/Microsoft/hcsshim/internal/shimdiag"
24	"github.com/Microsoft/hcsshim/internal/uvm"
25	"github.com/Microsoft/hcsshim/osversion"
26	eventstypes "github.com/containerd/containerd/api/events"
27	"github.com/containerd/containerd/errdefs"
28	"github.com/containerd/containerd/runtime"
29	"github.com/containerd/containerd/runtime/v2/task"
30	"github.com/containerd/typeurl"
31	specs "github.com/opencontainers/runtime-spec/specs-go"
32	"github.com/pkg/errors"
33	"github.com/sirupsen/logrus"
34	"go.opencensus.io/trace"
35)
36
37func newHcsStandaloneTask(ctx context.Context, events publisher, req *task.CreateTaskRequest, s *specs.Spec) (shimTask, error) {
38	log.G(ctx).WithField("tid", req.ID).Debug("newHcsStandaloneTask")
39
40	ct, _, err := oci.GetSandboxTypeAndID(s.Annotations)
41	if err != nil {
42		return nil, err
43	}
44	if ct != oci.KubernetesContainerTypeNone {
45		return nil, errors.Wrapf(
46			errdefs.ErrFailedPrecondition,
47			"cannot create standalone task, expected no annotation: '%s': got '%s'",
48			oci.KubernetesContainerTypeAnnotation,
49			ct)
50	}
51
52	owner := filepath.Base(os.Args[0])
53
54	var parent *uvm.UtilityVM
55	if osversion.Get().Build >= osversion.RS5 && oci.IsIsolated(s) {
56		// Create the UVM parent
57		opts, err := oci.SpecToUVMCreateOpts(ctx, s, fmt.Sprintf("%s@vm", req.ID), owner)
58		if err != nil {
59			return nil, err
60		}
61		switch opts.(type) {
62		case *uvm.OptionsLCOW:
63			lopts := (opts).(*uvm.OptionsLCOW)
64			parent, err = uvm.CreateLCOW(ctx, lopts)
65			if err != nil {
66				return nil, err
67			}
68		case *uvm.OptionsWCOW:
69			wopts := (opts).(*uvm.OptionsWCOW)
70
71			// In order for the UVM sandbox.vhdx not to collide with the actual
72			// nested Argon sandbox.vhdx we append the \vm folder to the last
73			// entry in the list.
74			layersLen := len(s.Windows.LayerFolders)
75			layers := make([]string, layersLen)
76			copy(layers, s.Windows.LayerFolders)
77
78			vmPath := filepath.Join(layers[layersLen-1], "vm")
79			err := os.MkdirAll(vmPath, 0)
80			if err != nil {
81				return nil, err
82			}
83			layers[layersLen-1] = vmPath
84			wopts.LayerFolders = layers
85
86			parent, err = uvm.CreateWCOW(ctx, wopts)
87			if err != nil {
88				return nil, err
89			}
90		}
91		err = parent.Start(ctx)
92		if err != nil {
93			parent.Close()
94		}
95	} else if !oci.IsWCOW(s) {
96		return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "oci spec does not contain WCOW or LCOW spec")
97	}
98
99	shim, err := newHcsTask(ctx, events, parent, true, req, s)
100	if err != nil {
101		if parent != nil {
102			parent.Close()
103		}
104		return nil, err
105	}
106	return shim, nil
107}
108
109// newHcsTask creates a container within `parent` and its init exec process in
110// the `shimExecCreated` state and returns the task that tracks its lifetime.
111//
112// If `parent == nil` the container is created on the host.
113func newHcsTask(
114	ctx context.Context,
115	events publisher,
116	parent *uvm.UtilityVM,
117	ownsParent bool,
118	req *task.CreateTaskRequest,
119	s *specs.Spec) (_ shimTask, err error) {
120	log.G(ctx).WithFields(logrus.Fields{
121		"tid":        req.ID,
122		"ownsParent": ownsParent,
123	}).Debug("newHcsTask")
124
125	owner := filepath.Base(os.Args[0])
126
127	io, err := cmd.NewNpipeIO(ctx, req.Stdin, req.Stdout, req.Stderr, req.Terminal)
128	if err != nil {
129		return nil, err
130	}
131
132	var netNS string
133	if s.Windows != nil &&
134		s.Windows.Network != nil {
135		netNS = s.Windows.Network.NetworkNamespace
136	}
137
138	var shimOpts *runhcsopts.Options
139	if req.Options != nil {
140		v, err := typeurl.UnmarshalAny(req.Options)
141		if err != nil {
142			return nil, err
143		}
144		shimOpts = v.(*runhcsopts.Options)
145	}
146
147	opts := hcsoci.CreateOptions{
148		ID:               req.ID,
149		Owner:            owner,
150		Spec:             s,
151		HostingSystem:    parent,
152		NetworkNamespace: netNS,
153	}
154
155	if shimOpts != nil {
156		opts.ScaleCPULimitsToSandbox = shimOpts.ScaleCpuLimitsToSandbox
157	}
158
159	system, resources, err := hcsoci.CreateContainer(ctx, &opts)
160	if err != nil {
161		return nil, err
162	}
163
164	ht := &hcsTask{
165		events:   events,
166		id:       req.ID,
167		isWCOW:   oci.IsWCOW(s),
168		c:        system,
169		cr:       resources,
170		ownsHost: ownsParent,
171		host:     parent,
172		closed:   make(chan struct{}),
173	}
174	ht.init = newHcsExec(
175		ctx,
176		events,
177		req.ID,
178		parent,
179		system,
180		req.ID,
181		req.Bundle,
182		ht.isWCOW,
183		s.Process,
184		io)
185
186	if parent != nil {
187		// We have a parent UVM. Listen for its exit and forcibly close this
188		// task. This is not expected but in the event of a UVM crash we need to
189		// handle this case.
190		go ht.waitForHostExit()
191	}
192	// In the normal case the `Signal` call from the caller killed this task's
193	// init process.
194	go ht.waitInitExit()
195
196	// Publish the created event
197	ht.events.publishEvent(
198		ctx,
199		runtime.TaskCreateEventTopic,
200		&eventstypes.TaskCreate{
201			ContainerID: req.ID,
202			Bundle:      req.Bundle,
203			Rootfs:      req.Rootfs,
204			IO: &eventstypes.TaskIO{
205				Stdin:    req.Stdin,
206				Stdout:   req.Stdout,
207				Stderr:   req.Stderr,
208				Terminal: req.Terminal,
209			},
210			Checkpoint: "",
211			Pid:        uint32(ht.init.Pid()),
212		})
213	return ht, nil
214}
215
216var _ = (shimTask)(&hcsTask{})
217
218// hcsTask is a generic task that represents a WCOW Container (process or
219// hypervisor isolated), or a LCOW Container. This task MAY own the UVM the
220// container is in but in the case of a POD it may just track the UVM for
221// container lifetime management. In the case of ownership when the init
222// task/exec is stopped the UVM itself will be stopped as well.
223type hcsTask struct {
224	events publisher
225	// id is the id of this task when it is created.
226	//
227	// It MUST be treated as read only in the liftetime of the task.
228	id string
229	// isWCOW is set to `true` if this is a task representing a Windows container.
230	//
231	// It MUST be treated as read only in the liftetime of the task.
232	isWCOW bool
233	// c is the container backing this task.
234	//
235	// It MUST be treated as read only in the lifetime of this task EXCEPT after
236	// a Kill to the init task in which it must be shutdown.
237	c cow.Container
238	// cr is the container resources this task is holding.
239	//
240	// It MUST be treated as read only in the lifetime of this task EXCEPT after
241	// a Kill to the init task in which all resources must be released.
242	cr *resources.Resources
243	// init is the init process of the container.
244	//
245	// Note: the invariant `container state == init.State()` MUST be true. IE:
246	// if the init process exits the container as a whole and all exec's MUST
247	// exit.
248	//
249	// It MUST be treated as read only in the lifetime of the task.
250	init shimExec
251	// ownsHost is `true` if this task owns `host`. If so when this tasks init
252	// exec shuts down it is required that `host` be shut down as well.
253	ownsHost bool
254	// host is the hosting VM for this exec if hypervisor isolated. If
255	// `host==nil` this is an Argon task so no UVM cleanup is required.
256	//
257	// NOTE: if `osversion.Get().Build < osversion.RS5` this will always be
258	// `nil`.
259	host *uvm.UtilityVM
260
261	// ecl is the exec create lock for all non-init execs and MUST be held
262	// durring create to prevent ID duplication.
263	ecl   sync.Mutex
264	execs sync.Map
265
266	closed    chan struct{}
267	closeOnce sync.Once
268	// closeHostOnce is used to close `host`. This will only be used if
269	// `ownsHost==true` and `host != nil`.
270	closeHostOnce sync.Once
271}
272
273func (ht *hcsTask) ID() string {
274	return ht.id
275}
276
277func (ht *hcsTask) CreateExec(ctx context.Context, req *task.ExecProcessRequest, spec *specs.Process) error {
278	ht.ecl.Lock()
279	defer ht.ecl.Unlock()
280
281	// If the task exists or we got a request for "" which is the init task
282	// fail.
283	if _, loaded := ht.execs.Load(req.ExecID); loaded || req.ExecID == "" {
284		return errors.Wrapf(errdefs.ErrAlreadyExists, "exec: '%s' in task: '%s' already exists", req.ExecID, ht.id)
285	}
286
287	if ht.init.State() != shimExecStateRunning {
288		return errors.Wrapf(errdefs.ErrFailedPrecondition, "exec: '' in task: '%s' must be running to create additional execs", ht.id)
289	}
290
291	io, err := cmd.NewNpipeIO(ctx, req.Stdin, req.Stdout, req.Stderr, req.Terminal)
292	if err != nil {
293		return err
294	}
295	he := newHcsExec(ctx, ht.events, ht.id, ht.host, ht.c, req.ExecID, ht.init.Status().Bundle, ht.isWCOW, spec, io)
296	ht.execs.Store(req.ExecID, he)
297
298	// Publish the created event
299	ht.events.publishEvent(
300		ctx,
301		runtime.TaskExecAddedEventTopic,
302		&eventstypes.TaskExecAdded{
303			ContainerID: ht.id,
304			ExecID:      req.ExecID,
305		})
306
307	return nil
308}
309
310func (ht *hcsTask) GetExec(eid string) (shimExec, error) {
311	if eid == "" {
312		return ht.init, nil
313	}
314	raw, loaded := ht.execs.Load(eid)
315	if !loaded {
316		return nil, errors.Wrapf(errdefs.ErrNotFound, "exec: '%s' in task: '%s' not found", eid, ht.id)
317	}
318	return raw.(shimExec), nil
319}
320
321func (ht *hcsTask) KillExec(ctx context.Context, eid string, signal uint32, all bool) error {
322	e, err := ht.GetExec(eid)
323	if err != nil {
324		return err
325	}
326	if all && eid != "" {
327		return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot signal all for non-empty exec: '%s'", eid)
328	}
329	if all {
330		// We are in a kill all on the init task. Signal everything.
331		ht.execs.Range(func(key, value interface{}) bool {
332			err := value.(shimExec).Kill(ctx, signal)
333			if err != nil {
334				log.G(ctx).WithFields(logrus.Fields{
335					"eid":           key,
336					logrus.ErrorKey: err,
337				}).Warn("failed to kill exec in task")
338			}
339
340			// iterate all
341			return false
342		})
343	}
344	if signal == 0x9 && eid == "" && ht.host != nil {
345		// If this is a SIGKILL against the init process we start a background
346		// timer and wait on either the timer expiring or the process exiting
347		// cleanly. If the timer exires first we forcibly close the UVM as we
348		// assume the guest is misbehaving for some reason.
349		go func() {
350			t := time.NewTimer(30 * time.Second)
351			execExited := make(chan struct{})
352			go func() {
353				e.Wait()
354				close(execExited)
355			}()
356			select {
357			case <-execExited:
358				t.Stop()
359			case <-t.C:
360				// Safe to call multiple times if called previously on
361				// successful shutdown.
362				ht.host.Close()
363			}
364		}()
365	}
366	return e.Kill(ctx, signal)
367}
368
369func (ht *hcsTask) DeleteExec(ctx context.Context, eid string) (int, uint32, time.Time, error) {
370	e, err := ht.GetExec(eid)
371	if err != nil {
372		return 0, 0, time.Time{}, err
373	}
374	if eid == "" {
375		// We are deleting the init exec. Forcibly exit any additional exec's.
376		ht.execs.Range(func(key, value interface{}) bool {
377			ex := value.(shimExec)
378			if s := ex.State(); s != shimExecStateExited {
379				ex.ForceExit(ctx, 1)
380			}
381
382			// iterate next
383			return false
384		})
385	}
386	switch state := e.State(); state {
387	case shimExecStateCreated:
388		e.ForceExit(ctx, 0)
389	case shimExecStateRunning:
390		return 0, 0, time.Time{}, newExecInvalidStateError(ht.id, eid, state, "delete")
391	}
392	status := e.Status()
393	if eid != "" {
394		ht.execs.Delete(eid)
395	}
396
397	// Publish the deleted event
398	ht.events.publishEvent(
399		ctx,
400		runtime.TaskDeleteEventTopic,
401		&eventstypes.TaskDelete{
402			ContainerID: ht.id,
403			ID:          eid,
404			Pid:         status.Pid,
405			ExitStatus:  status.ExitStatus,
406			ExitedAt:    status.ExitedAt,
407		})
408
409	return int(status.Pid), status.ExitStatus, status.ExitedAt, nil
410}
411
412func (ht *hcsTask) Pids(ctx context.Context) ([]options.ProcessDetails, error) {
413	// Map all user created exec's to pid/exec-id
414	pidMap := make(map[int]string)
415	ht.execs.Range(func(key, value interface{}) bool {
416		ex := value.(shimExec)
417		pidMap[ex.Pid()] = ex.ID()
418
419		// Iterate all
420		return false
421	})
422	pidMap[ht.init.Pid()] = ht.init.ID()
423
424	// Get the guest pids
425	props, err := ht.c.Properties(ctx, schema1.PropertyTypeProcessList)
426	if err != nil {
427		return nil, err
428	}
429
430	// Copy to pid/exec-id pair's
431	pairs := make([]options.ProcessDetails, len(props.ProcessList))
432	for i, p := range props.ProcessList {
433		pairs[i].ImageName = p.ImageName
434		pairs[i].CreatedAt = p.CreateTimestamp
435		pairs[i].KernelTime_100Ns = p.KernelTime100ns
436		pairs[i].MemoryCommitBytes = p.MemoryCommitBytes
437		pairs[i].MemoryWorkingSetPrivateBytes = p.MemoryWorkingSetPrivateBytes
438		pairs[i].MemoryWorkingSetSharedBytes = p.MemoryWorkingSetSharedBytes
439		pairs[i].ProcessID = p.ProcessId
440		pairs[i].UserTime_100Ns = p.KernelTime100ns
441
442		if eid, ok := pidMap[int(p.ProcessId)]; ok {
443			pairs[i].ExecID = eid
444		}
445	}
446	return pairs, nil
447}
448
449func (ht *hcsTask) Wait() *task.StateResponse {
450	<-ht.closed
451	return ht.init.Wait()
452}
453
454func (ht *hcsTask) waitInitExit() {
455	ctx, span := trace.StartSpan(context.Background(), "hcsTask::waitInitExit")
456	defer span.End()
457	span.AddAttributes(trace.StringAttribute("tid", ht.id))
458
459	// Wait for it to exit on its own
460	ht.init.Wait()
461
462	// Close the host and event the exit
463	ht.close(ctx)
464}
465
466// waitForHostExit waits for the host virtual machine to exit. Once exited
467// forcibly exits all additional exec's in this task.
468//
469// This MUST be called via a goroutine to wait on a background thread.
470//
471// Note: For Windows process isolated containers there is no host virtual
472// machine so this should not be called.
473func (ht *hcsTask) waitForHostExit() {
474	ctx, span := trace.StartSpan(context.Background(), "hcsTask::waitForHostExit")
475	defer span.End()
476	span.AddAttributes(trace.StringAttribute("tid", ht.id))
477
478	err := ht.host.Wait()
479	if err != nil {
480		log.G(ctx).WithError(err).Error("failed to wait for host virtual machine exit")
481	} else {
482		log.G(ctx).Debug("host virtual machine exited")
483	}
484
485	ht.execs.Range(func(key, value interface{}) bool {
486		ex := value.(shimExec)
487		ex.ForceExit(ctx, 1)
488
489		// iterate all
490		return false
491	})
492	ht.init.ForceExit(ctx, 1)
493	ht.closeHost(ctx)
494}
495
496// close shuts down the container that is owned by this task and if
497// `ht.ownsHost` will shutdown the hosting VM the container was placed in.
498//
499// NOTE: For Windows process isolated containers `ht.ownsHost==true && ht.host
500// == nil`.
501func (ht *hcsTask) close(ctx context.Context) {
502	ht.closeOnce.Do(func() {
503		log.G(ctx).Debug("hcsTask::closeOnce")
504
505		// ht.c should never be nil for a real task but in testing we stub
506		// this to avoid a nil dereference. We really should introduce a
507		// method or interface for ht.c operations that we can stub for
508		// testing.
509		if ht.c != nil {
510			// Do our best attempt to tear down the container.
511			var werr error
512			ch := make(chan struct{})
513			go func() {
514				werr = ht.c.Wait()
515				close(ch)
516			}()
517			err := ht.c.Shutdown(ctx)
518			if err != nil {
519				log.G(ctx).WithError(err).Error("failed to shutdown container")
520			} else {
521				t := time.NewTimer(time.Second * 30)
522				select {
523				case <-ch:
524					err = werr
525					t.Stop()
526					if err != nil {
527						log.G(ctx).WithError(err).Error("failed to wait for container shutdown")
528					}
529				case <-t.C:
530					log.G(ctx).WithError(hcs.ErrTimeout).Error("failed to wait for container shutdown")
531				}
532			}
533
534			if err != nil {
535				err = ht.c.Terminate(ctx)
536				if err != nil {
537					log.G(ctx).WithError(err).Error("failed to terminate container")
538				} else {
539					t := time.NewTimer(time.Second * 30)
540					select {
541					case <-ch:
542						err = werr
543						t.Stop()
544						if err != nil {
545							log.G(ctx).WithError(err).Error("failed to wait for container terminate")
546						}
547					case <-t.C:
548						log.G(ctx).WithError(hcs.ErrTimeout).Error("failed to wait for container terminate")
549					}
550				}
551			}
552
553			// Release any resources associated with the container.
554			if err := resources.ReleaseResources(ctx, ht.cr, ht.host, true); err != nil {
555				log.G(ctx).WithError(err).Error("failed to release container resources")
556			}
557
558			// Close the container handle invalidating all future access.
559			if err := ht.c.Close(); err != nil {
560				log.G(ctx).WithError(err).Error("failed to close container")
561			}
562		}
563		ht.closeHost(ctx)
564	})
565}
566
567// closeHost safely closes the hosting UVM if this task is the owner. Once
568// closed and all resources released it events the `runtime.TaskExitEventTopic`
569// for all upstream listeners.
570//
571// Note: If this is a process isolated task the hosting UVM is simply a `noop`.
572//
573// This call is idempotent and safe to call multiple times.
574func (ht *hcsTask) closeHost(ctx context.Context) {
575	ht.closeHostOnce.Do(func() {
576		log.G(ctx).Debug("hcsTask::closeHostOnce")
577
578		if ht.ownsHost && ht.host != nil {
579			if err := ht.host.Close(); err != nil {
580				log.G(ctx).WithError(err).Error("failed host vm shutdown")
581			}
582		}
583		// Send the `init` exec exit notification always.
584		exit := ht.init.Status()
585		ht.events.publishEvent(
586			ctx,
587			runtime.TaskExitEventTopic,
588			&eventstypes.TaskExit{
589				ContainerID: ht.id,
590				ID:          exit.ID,
591				Pid:         uint32(exit.Pid),
592				ExitStatus:  exit.ExitStatus,
593				ExitedAt:    exit.ExitedAt,
594			})
595		close(ht.closed)
596	})
597}
598
599func (ht *hcsTask) ExecInHost(ctx context.Context, req *shimdiag.ExecProcessRequest) (int, error) {
600	if ht.host == nil {
601		return cmd.ExecInShimHost(ctx, req)
602	}
603	return cmd.ExecInUvm(ctx, ht.host, req)
604}
605
606func (ht *hcsTask) DumpGuestStacks(ctx context.Context) string {
607	if ht.host != nil {
608		stacks, err := ht.host.DumpStacks(ctx)
609		if err != nil {
610			log.G(ctx).WithError(err).Warn("failed to capture guest stacks")
611		} else {
612			return stacks
613		}
614	}
615	return ""
616}
617
618func (ht *hcsTask) Share(ctx context.Context, req *shimdiag.ShareRequest) error {
619	if ht.host == nil {
620		return errTaskNotIsolated
621	}
622	// For hyper-v isolated WCOW the task used isn't the standard hcsTask so we
623	// only have to deal with the LCOW case here.
624	st, err := os.Stat(req.HostPath)
625	if err != nil {
626		return fmt.Errorf("could not open '%s' path on host: %s", req.HostPath, err)
627	}
628	var (
629		hostPath       string = req.HostPath
630		restrictAccess bool
631		fileName       string
632		allowedNames   []string
633	)
634	if !st.IsDir() {
635		hostPath, fileName = filepath.Split(hostPath)
636		allowedNames = append(allowedNames, fileName)
637		restrictAccess = true
638	}
639	_, err = ht.host.AddPlan9(ctx, hostPath, req.UvmPath, req.ReadOnly, restrictAccess, allowedNames)
640	return err
641}
642
643func hcsPropertiesToWindowsStats(props *hcsschema.Properties) *stats.Statistics_Windows {
644	wcs := &stats.Statistics_Windows{Windows: &stats.WindowsContainerStatistics{}}
645	if props.Statistics != nil {
646		wcs.Windows.Timestamp = props.Statistics.Timestamp
647		wcs.Windows.ContainerStartTime = props.Statistics.ContainerStartTime
648		wcs.Windows.UptimeNS = props.Statistics.Uptime100ns * 100
649		if props.Statistics.Processor != nil {
650			wcs.Windows.Processor = &stats.WindowsContainerProcessorStatistics{
651				TotalRuntimeNS:  props.Statistics.Processor.TotalRuntime100ns * 100,
652				RuntimeUserNS:   props.Statistics.Processor.RuntimeUser100ns * 100,
653				RuntimeKernelNS: props.Statistics.Processor.RuntimeKernel100ns * 100,
654			}
655		}
656		if props.Statistics.Memory != nil {
657			wcs.Windows.Memory = &stats.WindowsContainerMemoryStatistics{
658				MemoryUsageCommitBytes:            props.Statistics.Memory.MemoryUsageCommitBytes,
659				MemoryUsageCommitPeakBytes:        props.Statistics.Memory.MemoryUsageCommitPeakBytes,
660				MemoryUsagePrivateWorkingSetBytes: props.Statistics.Memory.MemoryUsagePrivateWorkingSetBytes,
661			}
662		}
663		if props.Statistics.Storage != nil {
664			wcs.Windows.Storage = &stats.WindowsContainerStorageStatistics{
665				ReadCountNormalized:  props.Statistics.Storage.ReadCountNormalized,
666				ReadSizeBytes:        props.Statistics.Storage.ReadSizeBytes,
667				WriteCountNormalized: props.Statistics.Storage.WriteCountNormalized,
668				WriteSizeBytes:       props.Statistics.Storage.WriteSizeBytes,
669			}
670		}
671	}
672	return wcs
673}
674
675func (ht *hcsTask) Stats(ctx context.Context) (*stats.Statistics, error) {
676	s := &stats.Statistics{}
677
678	props, err := ht.c.PropertiesV2(ctx, hcsschema.PTStatistics)
679	if err != nil {
680		return nil, err
681	}
682	if ht.isWCOW {
683		s.Container = hcsPropertiesToWindowsStats(props)
684	} else {
685		s.Container = &stats.Statistics_Linux{Linux: props.Metrics}
686	}
687	if ht.ownsHost && ht.host != nil {
688		vmStats, err := ht.host.Stats(ctx)
689		if err != nil {
690			return nil, err
691		}
692		s.VM = vmStats
693	}
694	return s, nil
695}
696