github.com/openshift/moby-moby@v1.13.2-0.20170601211448-f5ec1e2936dc/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/distribution/digest" 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/mount" 19 "github.com/docker/docker/pkg/plugins" 20 "github.com/docker/docker/pkg/stringid" 21 "github.com/docker/docker/plugin/v2" 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 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 if err := initlayer.Setup(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName), 0, 0); err != nil { 61 return errors.WithStack(err) 62 } 63 64 if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil { 65 if p.PropagatedMount != "" { 66 if err := mount.Unmount(p.PropagatedMount); err != nil { 67 logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) 68 } 69 if err := mount.Unmount(propRoot); err != nil { 70 logrus.Warnf("Could not unmount %s: %v", propRoot, err) 71 } 72 } 73 return errors.WithStack(err) 74 } 75 76 return pm.pluginPostStart(p, c) 77 } 78 79 func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error { 80 client, err := plugins.NewClientWithTimeout("unix://"+filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket()), nil, c.timeoutInSecs) 81 if err != nil { 82 c.restart = false 83 shutdownPlugin(p, c, pm.containerdClient) 84 return errors.WithStack(err) 85 } 86 87 p.SetPClient(client) 88 pm.config.Store.SetState(p, true) 89 pm.config.Store.CallHandler(p) 90 91 return pm.save(p) 92 } 93 94 func (pm *Manager) restore(p *v2.Plugin) error { 95 if err := pm.containerdClient.Restore(p.GetID(), attachToLog(p.GetID())); err != nil { 96 return err 97 } 98 99 if pm.config.LiveRestoreEnabled { 100 c := &controller{} 101 if pids, _ := pm.containerdClient.GetPidsForContainer(p.GetID()); len(pids) == 0 { 102 // plugin is not running, so follow normal startup procedure 103 return pm.enable(p, c, true) 104 } 105 106 c.exitChan = make(chan bool) 107 c.restart = true 108 pm.mu.Lock() 109 pm.cMap[p] = c 110 pm.mu.Unlock() 111 return pm.pluginPostStart(p, c) 112 } 113 114 return nil 115 } 116 117 func shutdownPlugin(p *v2.Plugin, c *controller, containerdClient libcontainerd.Client) { 118 pluginID := p.GetID() 119 120 err := containerdClient.Signal(pluginID, int(syscall.SIGTERM)) 121 if err != nil { 122 logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) 123 } else { 124 select { 125 case <-c.exitChan: 126 logrus.Debug("Clean shutdown of plugin") 127 case <-time.After(time.Second * 10): 128 logrus.Debug("Force shutdown plugin") 129 if err := containerdClient.Signal(pluginID, int(syscall.SIGKILL)); err != nil { 130 logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) 131 } 132 } 133 } 134 } 135 136 func (pm *Manager) disable(p *v2.Plugin, c *controller) error { 137 if !p.IsEnabled() { 138 return fmt.Errorf("plugin %s is already disabled", p.Name()) 139 } 140 141 c.restart = false 142 shutdownPlugin(p, c, pm.containerdClient) 143 pm.config.Store.SetState(p, false) 144 return pm.save(p) 145 } 146 147 // Shutdown stops all plugins and called during daemon shutdown. 148 func (pm *Manager) Shutdown() { 149 plugins := pm.config.Store.GetAll() 150 for _, p := range plugins { 151 pm.mu.RLock() 152 c := pm.cMap[p] 153 pm.mu.RUnlock() 154 155 if pm.config.LiveRestoreEnabled && p.IsEnabled() { 156 logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") 157 continue 158 } 159 if pm.containerdClient != nil && p.IsEnabled() { 160 c.restart = false 161 shutdownPlugin(p, c, pm.containerdClient) 162 } 163 } 164 } 165 166 func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) { 167 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 168 if err != nil { 169 return err 170 } 171 172 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 173 orig := filepath.Join(pdir, "rootfs") 174 175 // Make sure nothing is mounted 176 // This could happen if the plugin was disabled with `-f` with active mounts. 177 // If there is anything in `orig` is still mounted, this should error out. 178 if err := recursiveUnmount(orig); err != nil { 179 return err 180 } 181 182 backup := orig + "-old" 183 if err := os.Rename(orig, backup); err != nil { 184 return errors.Wrap(err, "error backing up plugin data before upgrade") 185 } 186 187 defer func() { 188 if err != nil { 189 if rmErr := os.RemoveAll(orig); rmErr != nil && !os.IsNotExist(rmErr) { 190 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade") 191 return 192 } 193 194 if err := os.Rename(backup, orig); err != nil { 195 err = errors.Wrap(err, "error restoring old plugin root on upgrade failure") 196 } 197 if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) { 198 logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir) 199 } 200 } else { 201 if rmErr := os.RemoveAll(backup); rmErr != nil && !os.IsNotExist(rmErr) { 202 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade") 203 } 204 205 p.Config = configDigest 206 p.Blobsums = blobsums 207 } 208 }() 209 210 if err := os.Rename(tmpRootFSDir, orig); err != nil { 211 return errors.Wrap(err, "error upgrading") 212 } 213 214 p.PluginObj.Config = config 215 err = pm.save(p) 216 return errors.Wrap(err, "error saving upgraded plugin config") 217 } 218 219 func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) { 220 configRC, err := pm.blobStore.Get(configDigest) 221 if err != nil { 222 return types.PluginConfig{}, err 223 } 224 defer configRC.Close() 225 226 var config types.PluginConfig 227 dec := json.NewDecoder(configRC) 228 if err := dec.Decode(&config); err != nil { 229 return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config") 230 } 231 if dec.More() { 232 return types.PluginConfig{}, errors.New("invalid config json") 233 } 234 235 requiredPrivileges, err := computePrivileges(config) 236 if err != nil { 237 return types.PluginConfig{}, err 238 } 239 if privileges != nil { 240 if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { 241 return types.PluginConfig{}, err 242 } 243 } 244 245 return config, nil 246 } 247 248 // createPlugin creates a new plugin. take lock before calling. 249 func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges) (p *v2.Plugin, err error) { 250 if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store 251 return nil, err 252 } 253 254 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 255 if err != nil { 256 return nil, err 257 } 258 259 p = &v2.Plugin{ 260 PluginObj: types.Plugin{ 261 Name: name, 262 ID: stringid.GenerateRandomID(), 263 Config: config, 264 }, 265 Config: configDigest, 266 Blobsums: blobsums, 267 } 268 p.InitEmptySettings() 269 270 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 271 if err := os.MkdirAll(pdir, 0700); err != nil { 272 return nil, errors.Wrapf(err, "failed to mkdir %v", pdir) 273 } 274 275 defer func() { 276 if err != nil { 277 os.RemoveAll(pdir) 278 } 279 }() 280 281 if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil { 282 return nil, errors.Wrap(err, "failed to rename rootfs") 283 } 284 285 if err := pm.save(p); err != nil { 286 return nil, err 287 } 288 289 pm.config.Store.Add(p) // todo: remove 290 291 return p, nil 292 }