github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/plugin/manager_linux.go (about) 1 // +build linux 2 3 package plugin 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "os" 9 "path/filepath" 10 "syscall" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/daemon/initlayer" 16 "github.com/docker/docker/libcontainerd" 17 "github.com/docker/docker/pkg/mount" 18 "github.com/docker/docker/pkg/plugins" 19 "github.com/docker/docker/pkg/stringid" 20 "github.com/docker/docker/plugin/v2" 21 "github.com/opencontainers/go-digest" 22 specs "github.com/opencontainers/runtime-spec/specs-go" 23 "github.com/pkg/errors" 24 ) 25 26 func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error { 27 p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs") 28 if p.IsEnabled() && !force { 29 return fmt.Errorf("plugin %s is already enabled", p.Name()) 30 } 31 spec, err := p.InitSpec(pm.config.ExecRoot) 32 if err != nil { 33 return err 34 } 35 36 c.restart = true 37 c.exitChan = make(chan bool) 38 39 pm.mu.Lock() 40 pm.cMap[p] = c 41 pm.mu.Unlock() 42 43 if p.PropagatedMount != "" { 44 if err := mount.MakeRShared(p.PropagatedMount); err != nil { 45 return err 46 } 47 } 48 49 if err := initlayer.Setup(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName), 0, 0); err != nil { 50 return err 51 } 52 53 if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil { 54 if p.PropagatedMount != "" { 55 if err := mount.Unmount(p.PropagatedMount); err != nil { 56 logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) 57 } 58 } 59 return err 60 } 61 62 return pm.pluginPostStart(p, c) 63 } 64 65 func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error { 66 client, err := plugins.NewClientWithTimeout("unix://"+filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket()), nil, c.timeoutInSecs) 67 if err != nil { 68 c.restart = false 69 shutdownPlugin(p, c, pm.containerdClient) 70 return err 71 } 72 73 p.SetPClient(client) 74 pm.config.Store.SetState(p, true) 75 pm.config.Store.CallHandler(p) 76 77 return pm.save(p) 78 } 79 80 func (pm *Manager) restore(p *v2.Plugin) error { 81 if err := pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID())); err != nil { 82 return err 83 } 84 85 if pm.config.LiveRestoreEnabled { 86 c := &controller{} 87 if pids, _ := pm.containerdClient.GetPidsForContainer(p.GetID()); len(pids) == 0 { 88 // plugin is not running, so follow normal startup procedure 89 return pm.enable(p, c, true) 90 } 91 92 c.exitChan = make(chan bool) 93 c.restart = true 94 pm.mu.Lock() 95 pm.cMap[p] = c 96 pm.mu.Unlock() 97 return pm.pluginPostStart(p, c) 98 } 99 100 return nil 101 } 102 103 func shutdownPlugin(p *v2.Plugin, c *controller, containerdClient libcontainerd.Client) { 104 pluginID := p.GetID() 105 106 err := containerdClient.Signal(pluginID, int(syscall.SIGTERM)) 107 if err != nil { 108 logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) 109 } else { 110 select { 111 case <-c.exitChan: 112 logrus.Debug("Clean shutdown of plugin") 113 case <-time.After(time.Second * 10): 114 logrus.Debug("Force shutdown plugin") 115 if err := containerdClient.Signal(pluginID, int(syscall.SIGKILL)); err != nil { 116 logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) 117 } 118 } 119 } 120 } 121 122 func (pm *Manager) disable(p *v2.Plugin, c *controller) error { 123 if !p.IsEnabled() { 124 return fmt.Errorf("plugin %s is already disabled", p.Name()) 125 } 126 127 c.restart = false 128 shutdownPlugin(p, c, pm.containerdClient) 129 pm.config.Store.SetState(p, false) 130 return pm.save(p) 131 } 132 133 // Shutdown stops all plugins and called during daemon shutdown. 134 func (pm *Manager) Shutdown() { 135 plugins := pm.config.Store.GetAll() 136 for _, p := range plugins { 137 pm.mu.RLock() 138 c := pm.cMap[p] 139 pm.mu.RUnlock() 140 141 if pm.config.LiveRestoreEnabled && p.IsEnabled() { 142 logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") 143 continue 144 } 145 if pm.containerdClient != nil && p.IsEnabled() { 146 c.restart = false 147 shutdownPlugin(p, c, pm.containerdClient) 148 } 149 } 150 } 151 152 // createPlugin creates a new plugin. take lock before calling. 153 func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges) (p *v2.Plugin, err error) { 154 if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store 155 return nil, err 156 } 157 158 configRC, err := pm.blobStore.Get(configDigest) 159 if err != nil { 160 return nil, err 161 } 162 defer configRC.Close() 163 164 var config types.PluginConfig 165 dec := json.NewDecoder(configRC) 166 if err := dec.Decode(&config); err != nil { 167 return nil, errors.Wrapf(err, "failed to parse config") 168 } 169 if dec.More() { 170 return nil, errors.New("invalid config json") 171 } 172 173 requiredPrivileges, err := computePrivileges(config) 174 if err != nil { 175 return nil, err 176 } 177 if privileges != nil { 178 if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { 179 return nil, err 180 } 181 } 182 183 p = &v2.Plugin{ 184 PluginObj: types.Plugin{ 185 Name: name, 186 ID: stringid.GenerateRandomID(), 187 Config: config, 188 }, 189 Config: configDigest, 190 Blobsums: blobsums, 191 } 192 p.InitEmptySettings() 193 194 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 195 if err := os.MkdirAll(pdir, 0700); err != nil { 196 return nil, errors.Wrapf(err, "failed to mkdir %v", pdir) 197 } 198 199 defer func() { 200 if err != nil { 201 os.RemoveAll(pdir) 202 } 203 }() 204 205 if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil { 206 return nil, errors.Wrap(err, "failed to rename rootfs") 207 } 208 209 if err := pm.save(p); err != nil { 210 return nil, err 211 } 212 213 pm.config.Store.Add(p) // todo: remove 214 215 return p, nil 216 }