We need a separate API for handing the exit status and deletion of Exec'd processes to make sure they are properly cleaned up within the shim and daemon. Fixes #973 Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
		
			
				
	
	
		
			230 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// +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 plugin.State{
 | 
						|
		Pid:    c.Pid(),
 | 
						|
		Status: c.Status(),
 | 
						|
	}, 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) DeleteProcess(ctx context.Context, pid uint32) (*plugin.Exit, error) {
 | 
						|
	var process *hcs.Process
 | 
						|
	for _, p := range c.ctr.Processes() {
 | 
						|
		if p.Pid() == pid {
 | 
						|
			process = p
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if process == nil {
 | 
						|
		return nil, fmt.Errorf("process %d not found", pid)
 | 
						|
	}
 | 
						|
	ec, err := process.ExitCode()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	process.Delete()
 | 
						|
	return &plugin.Exit{
 | 
						|
		Status:    ec,
 | 
						|
		Timestamp: process.ExitedAt(),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |