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