github.com/brandon-bethke-neudesic/moby@v1.13.1/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 backup := orig + "-old" 175 if err := os.Rename(orig, backup); err != nil { 176 return err 177 } 178 179 defer func() { 180 if err != nil { 181 if rmErr := os.RemoveAll(orig); rmErr != nil && !os.IsNotExist(rmErr) { 182 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade") 183 return 184 } 185 186 if err := os.Rename(backup, orig); err != nil { 187 err = errors.Wrap(err, "error restoring old plugin root on upgrade failure") 188 } 189 if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) { 190 logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir) 191 } 192 } else { 193 if rmErr := os.RemoveAll(backup); rmErr != nil && !os.IsNotExist(rmErr) { 194 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade") 195 } 196 197 p.Config = configDigest 198 p.Blobsums = blobsums 199 } 200 }() 201 202 if err := os.Rename(tmpRootFSDir, orig); err != nil { 203 return errors.Wrap(err, "error upgrading") 204 } 205 206 p.PluginObj.Config = config 207 err = pm.save(p) 208 return errors.Wrap(err, "error saving upgraded plugin config") 209 } 210 211 func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) { 212 configRC, err := pm.blobStore.Get(configDigest) 213 if err != nil { 214 return types.PluginConfig{}, err 215 } 216 defer configRC.Close() 217 218 var config types.PluginConfig 219 dec := json.NewDecoder(configRC) 220 if err := dec.Decode(&config); err != nil { 221 return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config") 222 } 223 if dec.More() { 224 return types.PluginConfig{}, errors.New("invalid config json") 225 } 226 227 requiredPrivileges, err := computePrivileges(config) 228 if err != nil { 229 return types.PluginConfig{}, err 230 } 231 if privileges != nil { 232 if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { 233 return types.PluginConfig{}, err 234 } 235 } 236 237 return config, nil 238 } 239 240 // createPlugin creates a new plugin. take lock before calling. 241 func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges) (p *v2.Plugin, err error) { 242 if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store 243 return nil, err 244 } 245 246 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 247 if err != nil { 248 return nil, err 249 } 250 251 p = &v2.Plugin{ 252 PluginObj: types.Plugin{ 253 Name: name, 254 ID: stringid.GenerateRandomID(), 255 Config: config, 256 }, 257 Config: configDigest, 258 Blobsums: blobsums, 259 } 260 p.InitEmptySettings() 261 262 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 263 if err := os.MkdirAll(pdir, 0700); err != nil { 264 return nil, errors.Wrapf(err, "failed to mkdir %v", pdir) 265 } 266 267 defer func() { 268 if err != nil { 269 os.RemoveAll(pdir) 270 } 271 }() 272 273 if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil { 274 return nil, errors.Wrap(err, "failed to rename rootfs") 275 } 276 277 if err := pm.save(p); err != nil { 278 return nil, err 279 } 280 281 pm.config.Store.Add(p) // todo: remove 282 283 return p, nil 284 }