// +build linux,experimental package plugin import ( "os" "path/filepath" "syscall" "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/libcontainerd" "github.com/docker/docker/oci" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/system" "github.com/docker/docker/restartmanager" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/container" "github.com/opencontainers/specs/specs-go" ) func (pm *Manager) enable(p *plugin) error { spec, err := pm.initSpec(p) if err != nil { return err } p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0) if err := pm.containerdClient.Create(p.P.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only return err } socket := p.P.Manifest.Interface.Socket p.client, err = plugins.NewClient("unix://"+filepath.Join(p.runtimeSourcePath, socket), nil) if err != nil { return err } //TODO: check net.Dial pm.Lock() // fixme: lock single record p.P.Active = true pm.save() pm.Unlock() for _, typ := range p.P.Manifest.Interface.Types { if handler := pm.handlers[typ.String()]; handler != nil { handler(p.Name(), p.Client()) } } return nil } func (pm *Manager) restore(p *plugin) error { p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0) return pm.containerdClient.Restore(p.P.ID, libcontainerd.WithRestartManager(p.restartManager)) } func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) { s := oci.DefaultSpec() rootfs := filepath.Join(pm.libRoot, p.P.ID, "rootfs") s.Root = specs.Root{ Path: rootfs, Readonly: false, // TODO: all plugins should be readonly? settable in manifest? } mounts := append(p.P.Config.Mounts, types.PluginMount{ Source: &p.runtimeSourcePath, Destination: defaultPluginRuntimeDestination, Type: "bind", Options: []string{"rbind", "rshared"}, }) for _, mount := range mounts { m := specs.Mount{ Destination: mount.Destination, Type: mount.Type, Options: mount.Options, } // TODO: if nil, then it's required and user didn't set it if mount.Source != nil { m.Source = *mount.Source } if m.Source != "" && m.Type == "bind" { fi, err := os.Lstat(filepath.Join(rootfs, string(os.PathSeparator), m.Destination)) // TODO: followsymlinks if err != nil { return nil, err } if fi.IsDir() { if err := os.MkdirAll(m.Source, 0700); err != nil { return nil, err } } } s.Mounts = append(s.Mounts, m) } envs := make([]string, 1, len(p.P.Config.Env)+1) envs[0] = "PATH=" + system.DefaultPathEnv envs = append(envs, p.P.Config.Env...) args := append(p.P.Manifest.Entrypoint, p.P.Config.Args...) cwd := p.P.Manifest.Workdir if len(cwd) == 0 { cwd = "/" } s.Process = specs.Process{ Terminal: false, Args: args, Cwd: cwd, Env: envs, } return &s, nil } func (pm *Manager) disable(p *plugin) error { if err := p.restartManager.Cancel(); err != nil { logrus.Error(err) } if err := pm.containerdClient.Signal(p.P.ID, int(syscall.SIGKILL)); err != nil { logrus.Error(err) } os.RemoveAll(p.runtimeSourcePath) pm.Lock() // fixme: lock single record defer pm.Unlock() p.P.Active = false pm.save() return nil } // Shutdown stops all plugins and called during daemon shutdown. func (pm *Manager) Shutdown() { pm.RLock() defer pm.RUnlock() pm.shutdown = true for _, p := range pm.plugins { if p.restartManager != nil { if err := p.restartManager.Cancel(); err != nil { logrus.Error(err) } } if pm.containerdClient != nil { p.exitChan = make(chan bool) err := pm.containerdClient.Signal(p.P.ID, int(syscall.SIGTERM)) if err != nil { logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) } else { select { case <-p.exitChan: logrus.Debug("Clean shutdown of plugin") case <-time.After(time.Second * 10): logrus.Debug("Force shutdown plugin") if err := pm.containerdClient.Signal(p.P.ID, int(syscall.SIGKILL)); err != nil { logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) } } } close(p.exitChan) } if err := os.RemoveAll(p.runtimeSourcePath); err != nil { logrus.Errorf("Remove plugin runtime failed with error: %v", err) } } }