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