github.com/OpenFlowLabs/moby@v17.12.1-ce-rc2+incompatible/plugin/manager_linux.go (about) 1 // +build linux 2 3 package plugin 4 5 import ( 6 "encoding/json" 7 "net" 8 "os" 9 "path/filepath" 10 "time" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/daemon/initlayer" 14 "github.com/docker/docker/pkg/containerfs" 15 "github.com/docker/docker/pkg/idtools" 16 "github.com/docker/docker/pkg/mount" 17 "github.com/docker/docker/pkg/plugins" 18 "github.com/docker/docker/pkg/stringid" 19 "github.com/docker/docker/plugin/v2" 20 "github.com/opencontainers/go-digest" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 "golang.org/x/sys/unix" 24 ) 25 26 func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) (err error) { 27 p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs") 28 if p.IsEnabled() && !force { 29 return errors.Wrap(enabledError(p.Name()), "plugin already enabled") 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 var propRoot string 44 if p.PropagatedMount != "" { 45 propRoot = filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") 46 47 if err = os.MkdirAll(propRoot, 0755); err != nil { 48 logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err) 49 } 50 51 if err = mount.MakeRShared(propRoot); err != nil { 52 return errors.Wrap(err, "error setting up propagated mount dir") 53 } 54 55 if err = mount.Mount(propRoot, p.PropagatedMount, "none", "rbind"); err != nil { 56 return errors.Wrap(err, "error creating mount for propagated mount") 57 } 58 } 59 60 rootFS := containerfs.NewLocalContainerFS(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName)) 61 if err := initlayer.Setup(rootFS, idtools.IDPair{0, 0}); err != nil { 62 return errors.WithStack(err) 63 } 64 65 stdout, stderr := makeLoggerStreams(p.GetID()) 66 if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil { 67 if p.PropagatedMount != "" { 68 if err := mount.Unmount(p.PropagatedMount); err != nil { 69 logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) 70 } 71 if err := mount.Unmount(propRoot); err != nil { 72 logrus.Warnf("Could not unmount %s: %v", propRoot, err) 73 } 74 } 75 } 76 77 return pm.pluginPostStart(p, c) 78 } 79 80 func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error { 81 sockAddr := filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket()) 82 client, err := plugins.NewClientWithTimeout("unix://"+sockAddr, nil, time.Duration(c.timeoutInSecs)*time.Second) 83 if err != nil { 84 c.restart = false 85 shutdownPlugin(p, c, pm.executor) 86 return errors.WithStack(err) 87 } 88 89 p.SetPClient(client) 90 91 // Initial sleep before net Dial to allow plugin to listen on socket. 92 time.Sleep(500 * time.Millisecond) 93 maxRetries := 3 94 var retries int 95 for { 96 // net dial into the unix socket to see if someone's listening. 97 conn, err := net.Dial("unix", sockAddr) 98 if err == nil { 99 conn.Close() 100 break 101 } 102 103 time.Sleep(3 * time.Second) 104 retries++ 105 106 if retries > maxRetries { 107 logrus.Debugf("error net dialing plugin: %v", err) 108 c.restart = false 109 // While restoring plugins, we need to explicitly set the state to disabled 110 pm.config.Store.SetState(p, false) 111 shutdownPlugin(p, c, pm.executor) 112 return err 113 } 114 115 } 116 pm.config.Store.SetState(p, true) 117 pm.config.Store.CallHandler(p) 118 119 return pm.save(p) 120 } 121 122 func (pm *Manager) restore(p *v2.Plugin) error { 123 stdout, stderr := makeLoggerStreams(p.GetID()) 124 if err := pm.executor.Restore(p.GetID(), stdout, stderr); err != nil { 125 return err 126 } 127 128 if pm.config.LiveRestoreEnabled { 129 c := &controller{} 130 if isRunning, _ := pm.executor.IsRunning(p.GetID()); !isRunning { 131 // plugin is not running, so follow normal startup procedure 132 return pm.enable(p, c, true) 133 } 134 135 c.exitChan = make(chan bool) 136 c.restart = true 137 pm.mu.Lock() 138 pm.cMap[p] = c 139 pm.mu.Unlock() 140 return pm.pluginPostStart(p, c) 141 } 142 143 return nil 144 } 145 146 func shutdownPlugin(p *v2.Plugin, c *controller, executor Executor) { 147 pluginID := p.GetID() 148 149 err := executor.Signal(pluginID, int(unix.SIGTERM)) 150 if err != nil { 151 logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) 152 } else { 153 select { 154 case <-c.exitChan: 155 logrus.Debug("Clean shutdown of plugin") 156 case <-time.After(time.Second * 10): 157 logrus.Debug("Force shutdown plugin") 158 if err := executor.Signal(pluginID, int(unix.SIGKILL)); err != nil { 159 logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) 160 } 161 select { 162 case <-c.exitChan: 163 logrus.Debug("SIGKILL plugin shutdown") 164 case <-time.After(time.Second * 10): 165 logrus.Debug("Force shutdown plugin FAILED") 166 } 167 } 168 } 169 } 170 171 func setupRoot(root string) error { 172 if err := mount.MakePrivate(root); err != nil { 173 return errors.Wrap(err, "error setting plugin manager root to private") 174 } 175 return nil 176 } 177 178 func (pm *Manager) disable(p *v2.Plugin, c *controller) error { 179 if !p.IsEnabled() { 180 return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled") 181 } 182 183 c.restart = false 184 shutdownPlugin(p, c, pm.executor) 185 pm.config.Store.SetState(p, false) 186 return pm.save(p) 187 } 188 189 // Shutdown stops all plugins and called during daemon shutdown. 190 func (pm *Manager) Shutdown() { 191 plugins := pm.config.Store.GetAll() 192 for _, p := range plugins { 193 pm.mu.RLock() 194 c := pm.cMap[p] 195 pm.mu.RUnlock() 196 197 if pm.config.LiveRestoreEnabled && p.IsEnabled() { 198 logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") 199 continue 200 } 201 if pm.executor != nil && p.IsEnabled() { 202 c.restart = false 203 shutdownPlugin(p, c, pm.executor) 204 } 205 } 206 mount.Unmount(pm.config.Root) 207 } 208 209 func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) { 210 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 211 if err != nil { 212 return err 213 } 214 215 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 216 orig := filepath.Join(pdir, "rootfs") 217 218 // Make sure nothing is mounted 219 // This could happen if the plugin was disabled with `-f` with active mounts. 220 // If there is anything in `orig` is still mounted, this should error out. 221 if err := mount.RecursiveUnmount(orig); err != nil { 222 return systemError{err} 223 } 224 225 backup := orig + "-old" 226 if err := os.Rename(orig, backup); err != nil { 227 return errors.Wrap(systemError{err}, "error backing up plugin data before upgrade") 228 } 229 230 defer func() { 231 if err != nil { 232 if rmErr := os.RemoveAll(orig); rmErr != nil && !os.IsNotExist(rmErr) { 233 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade") 234 return 235 } 236 if mvErr := os.Rename(backup, orig); mvErr != nil { 237 err = errors.Wrap(mvErr, "error restoring old plugin root on upgrade failure") 238 } 239 if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) { 240 logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir) 241 } 242 } else { 243 if rmErr := os.RemoveAll(backup); rmErr != nil && !os.IsNotExist(rmErr) { 244 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade") 245 } 246 247 p.Config = configDigest 248 p.Blobsums = blobsums 249 } 250 }() 251 252 if err := os.Rename(tmpRootFSDir, orig); err != nil { 253 return errors.Wrap(systemError{err}, "error upgrading") 254 } 255 256 p.PluginObj.Config = config 257 err = pm.save(p) 258 return errors.Wrap(err, "error saving upgraded plugin config") 259 } 260 261 func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) { 262 configRC, err := pm.blobStore.Get(configDigest) 263 if err != nil { 264 return types.PluginConfig{}, err 265 } 266 defer configRC.Close() 267 268 var config types.PluginConfig 269 dec := json.NewDecoder(configRC) 270 if err := dec.Decode(&config); err != nil { 271 return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config") 272 } 273 if dec.More() { 274 return types.PluginConfig{}, errors.New("invalid config json") 275 } 276 277 requiredPrivileges := computePrivileges(config) 278 if err != nil { 279 return types.PluginConfig{}, err 280 } 281 if privileges != nil { 282 if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { 283 return types.PluginConfig{}, err 284 } 285 } 286 287 return config, nil 288 } 289 290 // createPlugin creates a new plugin. take lock before calling. 291 func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges, opts ...CreateOpt) (p *v2.Plugin, err error) { 292 if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store 293 return nil, validationError{err} 294 } 295 296 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 297 if err != nil { 298 return nil, err 299 } 300 301 p = &v2.Plugin{ 302 PluginObj: types.Plugin{ 303 Name: name, 304 ID: stringid.GenerateRandomID(), 305 Config: config, 306 }, 307 Config: configDigest, 308 Blobsums: blobsums, 309 } 310 p.InitEmptySettings() 311 for _, o := range opts { 312 o(p) 313 } 314 315 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 316 if err := os.MkdirAll(pdir, 0700); err != nil { 317 return nil, errors.Wrapf(err, "failed to mkdir %v", pdir) 318 } 319 320 defer func() { 321 if err != nil { 322 os.RemoveAll(pdir) 323 } 324 }() 325 326 if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil { 327 return nil, errors.Wrap(err, "failed to rename rootfs") 328 } 329 330 if err := pm.save(p); err != nil { 331 return nil, err 332 } 333 334 pm.config.Store.Add(p) // todo: remove 335 336 return p, nil 337 }