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