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