c5843b7615
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
211 lines
4.9 KiB
Go
211 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()
|
|
}
|