github.com/rumpl/bof@v23.0.0-rc.2+incompatible/plugin/manager_linux.go (about) 1 package plugin // import "github.com/docker/docker/plugin" 2 3 import ( 4 "context" 5 "encoding/json" 6 "net" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/containerd/containerd/content" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/daemon/initlayer" 14 "github.com/docker/docker/errdefs" 15 "github.com/docker/docker/pkg/containerfs" 16 "github.com/docker/docker/pkg/idtools" 17 "github.com/docker/docker/pkg/plugins" 18 "github.com/docker/docker/pkg/stringid" 19 v2 "github.com/docker/docker/plugin/v2" 20 "github.com/moby/sys/mount" 21 "github.com/opencontainers/go-digest" 22 specs "github.com/opencontainers/image-spec/specs-go/v1" 23 "github.com/pkg/errors" 24 "github.com/sirupsen/logrus" 25 "golang.org/x/sys/unix" 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 errors.Wrap(enabledError(p.Name()), "plugin already enabled") 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.PluginObj.Config.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 58 rootFS := containerfs.NewLocalContainerFS(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName)) 59 if err := initlayer.Setup(rootFS, idtools.Identity{UID: 0, GID: 0}); err != nil { 60 return errors.WithStack(err) 61 } 62 63 stdout, stderr := makeLoggerStreams(p.GetID()) 64 if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil { 65 if p.PluginObj.Config.PropagatedMount != "" { 66 if err := mount.Unmount(propRoot); err != nil { 67 logrus.WithField("plugin", p.Name()).WithError(err).Warn("Failed to unmount vplugin propagated mount root") 68 } 69 } 70 return errors.WithStack(err) 71 } 72 return pm.pluginPostStart(p, c) 73 } 74 75 func (pm *Manager) pluginPostStart(p *v2.Plugin, c *controller) error { 76 sockAddr := filepath.Join(pm.config.ExecRoot, p.GetID(), p.GetSocket()) 77 p.SetTimeout(time.Duration(c.timeoutInSecs) * time.Second) 78 addr := &net.UnixAddr{Net: "unix", Name: sockAddr} 79 p.SetAddr(addr) 80 81 if p.Protocol() == plugins.ProtocolSchemeHTTPV1 { 82 client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, p.Timeout()) 83 if err != nil { 84 c.restart = false 85 shutdownPlugin(p, c.exitChan, pm.executor) 86 return errors.WithStack(err) 87 } 88 89 p.SetPClient(client) 90 } 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.exitChan, pm.executor) 113 return err 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, c *controller) error { 123 stdout, stderr := makeLoggerStreams(p.GetID()) 124 alive, err := pm.executor.Restore(p.GetID(), stdout, stderr) 125 if err != nil { 126 return err 127 } 128 129 if pm.config.LiveRestoreEnabled { 130 if !alive { 131 return pm.enable(p, c, true) 132 } 133 134 c.exitChan = make(chan bool) 135 c.restart = true 136 pm.mu.Lock() 137 pm.cMap[p] = c 138 pm.mu.Unlock() 139 return pm.pluginPostStart(p, c) 140 } 141 142 if alive { 143 // TODO(@cpuguy83): Should we always just re-attach to the running plugin instead of doing this? 144 c.restart = false 145 shutdownPlugin(p, c.exitChan, pm.executor) 146 } 147 148 return nil 149 } 150 151 const shutdownTimeout = 10 * time.Second 152 153 func shutdownPlugin(p *v2.Plugin, ec chan bool, executor Executor) { 154 pluginID := p.GetID() 155 156 if err := executor.Signal(pluginID, unix.SIGTERM); err != nil { 157 logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) 158 return 159 } 160 161 timeout := time.NewTimer(shutdownTimeout) 162 defer timeout.Stop() 163 164 select { 165 case <-ec: 166 logrus.Debug("Clean shutdown of plugin") 167 case <-timeout.C: 168 logrus.Debug("Force shutdown plugin") 169 if err := executor.Signal(pluginID, unix.SIGKILL); err != nil { 170 logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) 171 } 172 173 timeout.Reset(shutdownTimeout) 174 175 select { 176 case <-ec: 177 logrus.Debug("SIGKILL plugin shutdown") 178 case <-timeout.C: 179 logrus.WithField("plugin", p.Name).Warn("Force shutdown plugin FAILED") 180 } 181 } 182 } 183 184 func (pm *Manager) disable(p *v2.Plugin, c *controller) error { 185 if !p.IsEnabled() { 186 return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled") 187 } 188 189 c.restart = false 190 shutdownPlugin(p, c.exitChan, pm.executor) 191 pm.config.Store.SetState(p, false) 192 return pm.save(p) 193 } 194 195 // Shutdown stops all plugins and called during daemon shutdown. 196 func (pm *Manager) Shutdown() { 197 plugins := pm.config.Store.GetAll() 198 for _, p := range plugins { 199 pm.mu.RLock() 200 c := pm.cMap[p] 201 pm.mu.RUnlock() 202 203 if pm.config.LiveRestoreEnabled && p.IsEnabled() { 204 logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") 205 continue 206 } 207 if pm.executor != nil && p.IsEnabled() { 208 c.restart = false 209 shutdownPlugin(p, c.exitChan, pm.executor) 210 } 211 } 212 if err := mount.RecursiveUnmount(pm.config.Root); err != nil { 213 logrus.WithError(err).Warn("error cleaning up plugin mounts") 214 } 215 } 216 217 func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest, manifestDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) { 218 config, err := pm.setupNewPlugin(configDigest, privileges) 219 if err != nil { 220 return err 221 } 222 223 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 224 orig := filepath.Join(pdir, "rootfs") 225 226 // Make sure nothing is mounted 227 // This could happen if the plugin was disabled with `-f` with active mounts. 228 // If there is anything in `orig` is still mounted, this should error out. 229 if err := mount.RecursiveUnmount(orig); err != nil { 230 return errdefs.System(err) 231 } 232 233 backup := orig + "-old" 234 if err := os.Rename(orig, backup); err != nil { 235 return errors.Wrap(errdefs.System(err), "error backing up plugin data before upgrade") 236 } 237 238 defer func() { 239 if err != nil { 240 if rmErr := os.RemoveAll(orig); rmErr != nil { 241 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade") 242 return 243 } 244 if mvErr := os.Rename(backup, orig); mvErr != nil { 245 err = errors.Wrap(mvErr, "error restoring old plugin root on upgrade failure") 246 } 247 if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) { 248 logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir) 249 } 250 } else { 251 if rmErr := os.RemoveAll(backup); rmErr != nil { 252 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade") 253 } 254 255 p.Config = configDigest 256 p.Blobsums = blobsums 257 } 258 }() 259 260 if err := os.Rename(tmpRootFSDir, orig); err != nil { 261 return errors.Wrap(errdefs.System(err), "error upgrading") 262 } 263 264 p.PluginObj.Config = config 265 p.Manifest = manifestDigest 266 err = pm.save(p) 267 return errors.Wrap(err, "error saving upgraded plugin config") 268 } 269 270 func (pm *Manager) setupNewPlugin(configDigest digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) { 271 configRA, err := pm.blobStore.ReaderAt(context.TODO(), specs.Descriptor{Digest: configDigest}) 272 if err != nil { 273 return types.PluginConfig{}, err 274 } 275 defer configRA.Close() 276 277 configR := content.NewReader(configRA) 278 279 var config types.PluginConfig 280 dec := json.NewDecoder(configR) 281 if err := dec.Decode(&config); err != nil { 282 return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config") 283 } 284 if dec.More() { 285 return types.PluginConfig{}, errors.New("invalid config json") 286 } 287 288 requiredPrivileges := computePrivileges(config) 289 if privileges != nil { 290 if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { 291 return types.PluginConfig{}, err 292 } 293 } 294 295 return config, nil 296 } 297 298 // createPlugin creates a new plugin. take lock before calling. 299 func (pm *Manager) createPlugin(name string, configDigest, manifestDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges, opts ...CreateOpt) (p *v2.Plugin, err error) { 300 if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store 301 return nil, errdefs.InvalidParameter(err) 302 } 303 304 config, err := pm.setupNewPlugin(configDigest, privileges) 305 if err != nil { 306 return nil, err 307 } 308 309 p = &v2.Plugin{ 310 PluginObj: types.Plugin{ 311 Name: name, 312 ID: stringid.GenerateRandomID(), 313 Config: config, 314 }, 315 Config: configDigest, 316 Blobsums: blobsums, 317 Manifest: manifestDigest, 318 } 319 p.InitEmptySettings() 320 for _, o := range opts { 321 o(p) 322 } 323 324 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 325 if err := os.MkdirAll(pdir, 0700); err != nil { 326 return nil, errors.Wrapf(err, "failed to mkdir %v", pdir) 327 } 328 329 defer func() { 330 if err != nil { 331 os.RemoveAll(pdir) 332 } 333 }() 334 335 if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil { 336 return nil, errors.Wrap(err, "failed to rename rootfs") 337 } 338 339 if err := pm.save(p); err != nil { 340 return nil, err 341 } 342 343 pm.config.Store.Add(p) // todo: remove 344 345 return p, nil 346 } 347 348 func recursiveUnmount(target string) error { 349 return mount.RecursiveUnmount(target) 350 }