| // +build windows | |
| package windows | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "sync" | |
| "time" | |
| "github.com/containerd/containerd/log" | |
| "github.com/containerd/containerd/plugin" | |
| "github.com/containerd/containerd/windows/hcs" | |
| specs "github.com/opencontainers/runtime-spec/specs-go" | |
| "github.com/pkg/errors" | |
| winsys "golang.org/x/sys/windows" | |
| ) | |
| var ( | |
| ErrLoadedContainer = errors.New("loaded container can only be terminated") | |
| ) | |
| type eventCallback func(id string, evType plugin.EventType, pid, exitStatus uint32, exitedAt time.Time) | |
| func loadContainers(ctx context.Context, h *hcs.HCS, sendEvent eventCallback) ([]*container, error) { | |
| hCtr, err := h.LoadContainers(ctx) | |
| if err != nil { | |
| return nil, err | |
| } | |
| containers := make([]*container, 0) | |
| for _, c := range hCtr { | |
| containers = append(containers, &container{ | |
| ctr: c, | |
| status: plugin.RunningStatus, | |
| sendEvent: sendEvent, | |
| }) | |
| } | |
| return containers, nil | |
| } | |
| func newContainer(ctx context.Context, h *hcs.HCS, id string, spec RuntimeSpec, io plugin.IO, sendEvent eventCallback) (*container, error) { | |
| cio, err := hcs.NewIO(io.Stdin, io.Stdout, io.Stderr, io.Terminal) | |
| if err != nil { | |
| return nil, err | |
| } | |
| hcsCtr, err := h.CreateContainer(ctx, id, spec.OCISpec, spec.Configuration, cio) | |
| if err != nil { | |
| return nil, err | |
| } | |
| sendEvent(id, plugin.CreateEvent, hcsCtr.Pid(), 0, time.Time{}) | |
| return &container{ | |
| ctr: hcsCtr, | |
| status: plugin.CreatedStatus, | |
| sendEvent: sendEvent, | |
| }, nil | |
| } | |
| type container struct { | |
| sync.Mutex | |
| ctr *hcs.Container | |
| status plugin.Status | |
| sendEvent eventCallback | |
| } | |
| func (c *container) Info() plugin.TaskInfo { | |
| return plugin.TaskInfo{ | |
| ID: c.ctr.ID(), | |
| Runtime: runtimeName, | |
| } | |
| } | |
| func (c *container) Start(ctx context.Context) error { | |
| if c.ctr.Pid() == 0 { | |
| return ErrLoadedContainer | |
| } | |
| err := c.ctr.Start(ctx) | |
| if err != nil { | |
| return err | |
| } | |
| c.setStatus(plugin.RunningStatus) | |
| c.sendEvent(c.ctr.ID(), plugin.StartEvent, c.ctr.Pid(), 0, time.Time{}) | |
| // Wait for our process to terminate | |
| go func() { | |
| ec, err := c.ctr.ExitCode() | |
| if err != nil { | |
| log.G(ctx).Debug(err) | |
| } | |
| c.setStatus(plugin.StoppedStatus) | |
| c.sendEvent(c.ctr.ID(), plugin.ExitEvent, c.ctr.Pid(), ec, c.ctr.Processes()[0].ExitedAt()) | |
| }() | |
| return nil | |
| } | |
| func (c *container) Pause(ctx context.Context) error { | |
| if c.ctr.GetConfiguration().UseHyperV == false { | |
| return fmt.Errorf("Windows non-HyperV containers do not support pause") | |
| } | |
| return c.ctr.Pause() | |
| } | |
| func (c *container) Resume(ctx context.Context) error { | |
| if c.ctr.GetConfiguration().UseHyperV == false { | |
| return fmt.Errorf("Windows non-HyperV containers do not support resume") | |
| } | |
| return c.ctr.Resume() | |
| } | |
| func (c *container) State(ctx context.Context) (plugin.State, error) { | |
| return c, nil | |
| } | |
| func (c *container) Kill(ctx context.Context, signal uint32, pid uint32, all bool) error { | |
| if winsys.Signal(signal) == winsys.SIGKILL { | |
| return c.ctr.Kill(ctx) | |
| } | |
| return c.ctr.Stop(ctx) | |
| } | |
| func (c *container) Exec(ctx context.Context, opts plugin.ExecOpts) (plugin.Process, error) { | |
| if c.ctr.Pid() == 0 { | |
| return nil, ErrLoadedContainer | |
| } | |
| pio, err := hcs.NewIO(opts.IO.Stdin, opts.IO.Stdout, opts.IO.Stderr, opts.IO.Terminal) | |
| if err != nil { | |
| return nil, err | |
| } | |
| var procSpec specs.Process | |
| if err := json.Unmarshal(opts.Spec, &procSpec); err != nil { | |
| return nil, errors.Wrap(err, "failed to unmarshal oci spec") | |
| } | |
| p, err := c.ctr.AddProcess(ctx, procSpec, pio) | |
| if err != nil { | |
| return nil, err | |
| } | |
| go func() { | |
| ec, err := p.ExitCode() | |
| if err != nil { | |
| log.G(ctx).Debug(err) | |
| } | |
| c.sendEvent(c.ctr.ID(), plugin.ExitEvent, p.Pid(), ec, p.ExitedAt()) | |
| }() | |
| return &process{p}, nil | |
| } | |
| func (c *container) CloseStdin(ctx context.Context, pid uint32) error { | |
| return c.ctr.CloseStdin(ctx, pid) | |
| } | |
| func (c *container) Pty(ctx context.Context, pid uint32, size plugin.ConsoleSize) error { | |
| return c.ctr.Pty(ctx, pid, size) | |
| } | |
| func (c *container) Status() plugin.Status { | |
| return c.getStatus() | |
| } | |
| func (c *container) Pid() uint32 { | |
| return c.ctr.Pid() | |
| } | |
| func (c *container) Processes(ctx context.Context) ([]uint32, error) { | |
| pl, err := c.ctr.ProcessList() | |
| if err != nil { | |
| return nil, err | |
| } | |
| pids := make([]uint32, 0, len(pl)) | |
| for _, p := range pl { | |
| pids = append(pids, p.ProcessId) | |
| } | |
| return pids, nil | |
| } | |
| func (c *container) Checkpoint(ctx context.Context, opts plugin.CheckpointOpts) error { | |
| return fmt.Errorf("Windows containers do not support checkpoint") | |
| } | |
| func (c *container) setStatus(status plugin.Status) { | |
| c.Lock() | |
| c.status = status | |
| c.Unlock() | |
| } | |
| func (c *container) getStatus() plugin.Status { | |
| c.Lock() | |
| defer c.Unlock() | |
| return c.status | |
| } |