github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 digest "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 } 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, c *controller) error { 124 stdout, stderr := makeLoggerStreams(p.GetID()) 125 alive, err := pm.executor.Restore(p.GetID(), stdout, stderr) 126 if err != nil { 127 return err 128 } 129 130 if pm.config.LiveRestoreEnabled { 131 if !alive { 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 if alive { 144 // TODO(@cpuguy83): Should we always just re-attach to the running plugin instead of doing this? 145 c.restart = false 146 shutdownPlugin(p, c.exitChan, pm.executor) 147 } 148 149 return nil 150 } 151 152 const shutdownTimeout = 10 * time.Second 153 154 func shutdownPlugin(p *v2.Plugin, ec chan bool, executor Executor) { 155 pluginID := p.GetID() 156 157 err := executor.Signal(pluginID, int(unix.SIGTERM)) 158 if err != nil { 159 logrus.Errorf("Sending SIGTERM to plugin failed with error: %v", err) 160 } else { 161 162 timeout := time.NewTimer(shutdownTimeout) 163 defer timeout.Stop() 164 165 select { 166 case <-ec: 167 logrus.Debug("Clean shutdown of plugin") 168 case <-timeout.C: 169 logrus.Debug("Force shutdown plugin") 170 if err := executor.Signal(pluginID, int(unix.SIGKILL)); err != nil { 171 logrus.Errorf("Sending SIGKILL to plugin failed with error: %v", err) 172 } 173 174 timeout.Reset(shutdownTimeout) 175 176 select { 177 case <-ec: 178 logrus.Debug("SIGKILL plugin shutdown") 179 case <-timeout.C: 180 logrus.WithField("plugin", p.Name).Warn("Force shutdown plugin FAILED") 181 } 182 } 183 } 184 } 185 186 func (pm *Manager) disable(p *v2.Plugin, c *controller) error { 187 if !p.IsEnabled() { 188 return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled") 189 } 190 191 c.restart = false 192 shutdownPlugin(p, c.exitChan, pm.executor) 193 pm.config.Store.SetState(p, false) 194 return pm.save(p) 195 } 196 197 // Shutdown stops all plugins and called during daemon shutdown. 198 func (pm *Manager) Shutdown() { 199 plugins := pm.config.Store.GetAll() 200 for _, p := range plugins { 201 pm.mu.RLock() 202 c := pm.cMap[p] 203 pm.mu.RUnlock() 204 205 if pm.config.LiveRestoreEnabled && p.IsEnabled() { 206 logrus.Debug("Plugin active when liveRestore is set, skipping shutdown") 207 continue 208 } 209 if pm.executor != nil && p.IsEnabled() { 210 c.restart = false 211 shutdownPlugin(p, c.exitChan, pm.executor) 212 } 213 } 214 if err := mount.RecursiveUnmount(pm.config.Root); err != nil { 215 logrus.WithError(err).Warn("error cleaning up plugin mounts") 216 } 217 } 218 219 func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest, manifestDigest digest.Digest, blobsums []digest.Digest, tmpRootFSDir string, privileges *types.PluginPrivileges) (err error) { 220 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 221 if err != nil { 222 return err 223 } 224 225 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 226 orig := filepath.Join(pdir, "rootfs") 227 228 // Make sure nothing is mounted 229 // This could happen if the plugin was disabled with `-f` with active mounts. 230 // If there is anything in `orig` is still mounted, this should error out. 231 if err := mount.RecursiveUnmount(orig); err != nil { 232 return errdefs.System(err) 233 } 234 235 backup := orig + "-old" 236 if err := os.Rename(orig, backup); err != nil { 237 return errors.Wrap(errdefs.System(err), "error backing up plugin data before upgrade") 238 } 239 240 defer func() { 241 if err != nil { 242 if rmErr := os.RemoveAll(orig); rmErr != nil { 243 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up after failed upgrade") 244 return 245 } 246 if mvErr := os.Rename(backup, orig); mvErr != nil { 247 err = errors.Wrap(mvErr, "error restoring old plugin root on upgrade failure") 248 } 249 if rmErr := os.RemoveAll(tmpRootFSDir); rmErr != nil && !os.IsNotExist(rmErr) { 250 logrus.WithError(rmErr).WithField("plugin", p.Name()).Errorf("error cleaning up plugin upgrade dir: %s", tmpRootFSDir) 251 } 252 } else { 253 if rmErr := os.RemoveAll(backup); rmErr != nil { 254 logrus.WithError(rmErr).WithField("dir", backup).Error("error cleaning up old plugin root after successful upgrade") 255 } 256 257 p.Config = configDigest 258 p.Blobsums = blobsums 259 } 260 }() 261 262 if err := os.Rename(tmpRootFSDir, orig); err != nil { 263 return errors.Wrap(errdefs.System(err), "error upgrading") 264 } 265 266 p.PluginObj.Config = config 267 p.Manifest = manifestDigest 268 err = pm.save(p) 269 return errors.Wrap(err, "error saving upgraded plugin config") 270 } 271 272 func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.Digest, privileges *types.PluginPrivileges) (types.PluginConfig, error) { 273 configRA, err := pm.blobStore.ReaderAt(context.TODO(), specs.Descriptor{Digest: configDigest}) 274 if err != nil { 275 return types.PluginConfig{}, err 276 } 277 defer configRA.Close() 278 279 configR := content.NewReader(configRA) 280 281 var config types.PluginConfig 282 dec := json.NewDecoder(configR) 283 if err := dec.Decode(&config); err != nil { 284 return types.PluginConfig{}, errors.Wrapf(err, "failed to parse config") 285 } 286 if dec.More() { 287 return types.PluginConfig{}, errors.New("invalid config json") 288 } 289 290 requiredPrivileges := computePrivileges(config) 291 if privileges != nil { 292 if err := validatePrivileges(requiredPrivileges, *privileges); err != nil { 293 return types.PluginConfig{}, err 294 } 295 } 296 297 return config, nil 298 } 299 300 // createPlugin creates a new plugin. take lock before calling. 301 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) { 302 if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store 303 return nil, errdefs.InvalidParameter(err) 304 } 305 306 config, err := pm.setupNewPlugin(configDigest, blobsums, privileges) 307 if err != nil { 308 return nil, err 309 } 310 311 p = &v2.Plugin{ 312 PluginObj: types.Plugin{ 313 Name: name, 314 ID: stringid.GenerateRandomID(), 315 Config: config, 316 }, 317 Config: configDigest, 318 Blobsums: blobsums, 319 Manifest: manifestDigest, 320 } 321 p.InitEmptySettings() 322 for _, o := range opts { 323 o(p) 324 } 325 326 pdir := filepath.Join(pm.config.Root, p.PluginObj.ID) 327 if err := os.MkdirAll(pdir, 0700); err != nil { 328 return nil, errors.Wrapf(err, "failed to mkdir %v", pdir) 329 } 330 331 defer func() { 332 if err != nil { 333 os.RemoveAll(pdir) 334 } 335 }() 336 337 if err := os.Rename(rootFSDir, filepath.Join(pdir, rootFSFileName)); err != nil { 338 return nil, errors.Wrap(err, "failed to rename rootfs") 339 } 340 341 if err := pm.save(p); err != nil { 342 return nil, err 343 } 344 345 pm.config.Store.Add(p) // todo: remove 346 347 return p, nil 348 } 349 350 func recursiveUnmount(target string) error { 351 return mount.RecursiveUnmount(target) 352 }