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