containerd/windows/runtime.go

212 lines
4.9 KiB
Go

// +build windows
package windows
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"syscall"
"time"
"github.com/containerd/containerd"
"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"
"golang.org/x/net/context"
)
const (
runtimeName = "windows"
owner = "containerd"
configFilename = "config.json"
)
// Win32 error codes that are used for various workarounds
// These really should be ALL_CAPS to match golangs syscall library and standard
// Win32 error conventions, but golint insists on CamelCase.
const (
CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string
ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started
ErrorBadPathname = syscall.Errno(161) // The specified path is invalid
ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
)
func init() {
plugin.Register(runtimeName, &plugin.Registration{
Type: plugin.RuntimePlugin,
Init: New,
})
}
func New(ic *plugin.InitContext) (interface{}, error) {
c, cancel := context.WithCancel(ic.Context)
rootDir := filepath.Join(ic.Root, runtimeName)
if err := os.MkdirAll(rootDir, 0755); err != nil {
return nil, errors.Wrapf(err, "could not create state directory at %s", rootDir)
}
// Terminate all previous container that we may have started. We don't
// support restoring containers
ctrs, err := loadContainers(ic.Context, rootDir)
if err != nil {
return nil, err
}
for _, c := range ctrs {
c.remove(ic.Context)
}
// Try to delete the old state dir and recreate it
stateDir := filepath.Join(ic.State, runtimeName)
if err := os.RemoveAll(stateDir); err != nil {
log.G(c).WithError(err).Warnf("failed to cleanup old state directory at %s", stateDir)
}
if err := os.MkdirAll(stateDir, 0755); err != nil {
return nil, errors.Wrapf(err, "could not create state directory at %s", stateDir)
}
return &Runtime{
containers: make(map[string]*container),
containersPid: make(map[uint32]struct{}),
events: make(chan *containerd.Event, 2048),
eventsContext: c,
eventsCancel: cancel,
stateDir: stateDir,
rootDir: rootDir,
}, nil
}
type Runtime struct {
sync.Mutex
rootDir string
stateDir string
containers map[string]*container
containersPid map[uint32]struct{}
currentPid uint32
events chan *containerd.Event
eventsContext context.Context
eventsCancel func()
}
type RuntimeSpec struct {
// Spec is the OCI spec
OCISpec specs.Spec
// HCS specific options
hcs.Configuration
}
func (r *Runtime) Create(ctx context.Context, id string, opts containerd.CreateOpts) (containerd.Container, error) {
var rtSpec RuntimeSpec
if err := json.Unmarshal(opts.Spec, &rtSpec); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal oci spec")
}
pid, err := r.getPid()
if err != nil {
return nil, err
}
ctr, err := newContainer(id, r.rootDir, pid, rtSpec, opts.IO, func(id string, evType containerd.EventType, pid, exitStatus uint32) {
r.sendEvent(id, evType, pid, exitStatus)
})
if err != nil {
r.putPid(pid)
return nil, err
}
r.Lock()
r.containers[id] = ctr
r.containersPid[pid] = struct{}{}
r.Unlock()
r.sendEvent(id, containerd.CreateEvent, pid, 0)
return ctr, nil
}
func (r *Runtime) Delete(ctx context.Context, c containerd.Container) (uint32, error) {
wc, ok := c.(*container)
if !ok {
return 0, fmt.Errorf("container cannot be cast as *windows.container")
}
ec, err := wc.exitCode(ctx)
if err != nil {
ec = 255
log.G(ctx).WithError(err).Errorf("failed to retrieve exit code for container %s", c.Info().ID)
}
if err = wc.remove(ctx); err == nil {
r.Lock()
delete(r.containers, c.Info().ID)
r.Unlock()
}
r.putPid(wc.getRuntimePid())
return ec, err
}
func (r *Runtime) Containers() ([]containerd.Container, error) {
r.Lock()
list := make([]containerd.Container, len(r.containers))
for _, c := range r.containers {
list = append(list, c)
}
r.Unlock()
return list, nil
}
func (r *Runtime) Events(ctx context.Context) <-chan *containerd.Event {
return r.events
}
func (r *Runtime) sendEvent(id string, evType containerd.EventType, pid, exitStatus uint32) {
r.events <- &containerd.Event{
Timestamp: time.Now(),
Runtime: runtimeName,
Type: evType,
Pid: pid,
ID: id,
ExitStatus: exitStatus,
}
}
func (r *Runtime) getPid() (uint32, error) {
r.Lock()
defer r.Unlock()
pid := r.currentPid + 1
for pid != r.currentPid {
// 0 is reserved and invalid
if pid == 0 {
pid = 1
}
if _, ok := r.containersPid[pid]; !ok {
r.currentPid = pid
return pid, nil
}
pid++
}
return 0, errors.New("pid pool exhausted")
}
func (r *Runtime) putPid(pid uint32) {
r.Lock()
delete(r.containersPid, pid)
r.Unlock()
}