github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/plugin/manager.go (about) 1 package plugin 2 3 import ( 4 "encoding/json" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "reflect" 10 "regexp" 11 "strings" 12 "sync" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/image" 17 "github.com/docker/docker/layer" 18 "github.com/docker/docker/libcontainerd" 19 "github.com/docker/docker/pkg/ioutils" 20 "github.com/docker/docker/pkg/mount" 21 "github.com/docker/docker/plugin/v2" 22 "github.com/docker/docker/reference" 23 "github.com/docker/docker/registry" 24 "github.com/opencontainers/go-digest" 25 "github.com/pkg/errors" 26 ) 27 28 const configFileName = "config.json" 29 const rootFSFileName = "rootfs" 30 31 var validFullID = regexp.MustCompile(`^([a-f0-9]{64})$`) 32 33 func (pm *Manager) restorePlugin(p *v2.Plugin) error { 34 if p.IsEnabled() { 35 return pm.restore(p) 36 } 37 return nil 38 } 39 40 type eventLogger func(id, name, action string) 41 42 // ManagerConfig defines configuration needed to start new manager. 43 type ManagerConfig struct { 44 Store *Store // remove 45 Executor libcontainerd.Remote 46 RegistryService registry.Service 47 LiveRestoreEnabled bool // TODO: remove 48 LogPluginEvent eventLogger 49 Root string 50 ExecRoot string 51 } 52 53 // Manager controls the plugin subsystem. 54 type Manager struct { 55 config ManagerConfig 56 mu sync.RWMutex // protects cMap 57 muGC sync.RWMutex // protects blobstore deletions 58 cMap map[*v2.Plugin]*controller 59 containerdClient libcontainerd.Client 60 blobStore *basicBlobStore 61 } 62 63 // controller represents the manager's control on a plugin. 64 type controller struct { 65 restart bool 66 exitChan chan bool 67 timeoutInSecs int 68 } 69 70 // pluginRegistryService ensures that all resolved repositories 71 // are of the plugin class. 72 type pluginRegistryService struct { 73 registry.Service 74 } 75 76 func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) { 77 repoInfo, err = s.Service.ResolveRepository(name) 78 if repoInfo != nil { 79 repoInfo.Class = "plugin" 80 } 81 return 82 } 83 84 // NewManager returns a new plugin manager. 85 func NewManager(config ManagerConfig) (*Manager, error) { 86 if config.RegistryService != nil { 87 config.RegistryService = pluginRegistryService{config.RegistryService} 88 } 89 manager := &Manager{ 90 config: config, 91 } 92 if err := os.MkdirAll(manager.config.Root, 0700); err != nil { 93 return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.Root) 94 } 95 if err := os.MkdirAll(manager.config.ExecRoot, 0700); err != nil { 96 return nil, errors.Wrapf(err, "failed to mkdir %v", manager.config.ExecRoot) 97 } 98 if err := os.MkdirAll(manager.tmpDir(), 0700); err != nil { 99 return nil, errors.Wrapf(err, "failed to mkdir %v", manager.tmpDir()) 100 } 101 var err error 102 manager.containerdClient, err = config.Executor.Client(manager) // todo: move to another struct 103 if err != nil { 104 return nil, errors.Wrap(err, "failed to create containerd client") 105 } 106 manager.blobStore, err = newBasicBlobStore(filepath.Join(manager.config.Root, "storage/blobs")) 107 if err != nil { 108 return nil, err 109 } 110 111 manager.cMap = make(map[*v2.Plugin]*controller) 112 if err := manager.reload(); err != nil { 113 return nil, errors.Wrap(err, "failed to restore plugins") 114 } 115 return manager, nil 116 } 117 118 func (pm *Manager) tmpDir() string { 119 return filepath.Join(pm.config.Root, "tmp") 120 } 121 122 // StateChanged updates plugin internals using libcontainerd events. 123 func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error { 124 logrus.Debugf("plugin state changed %s %#v", id, e) 125 126 switch e.State { 127 case libcontainerd.StateExit: 128 p, err := pm.config.Store.GetV2Plugin(id) 129 if err != nil { 130 return err 131 } 132 133 pm.mu.RLock() 134 c := pm.cMap[p] 135 136 if c.exitChan != nil { 137 close(c.exitChan) 138 } 139 restart := c.restart 140 pm.mu.RUnlock() 141 142 os.RemoveAll(filepath.Join(pm.config.ExecRoot, id)) 143 144 if p.PropagatedMount != "" { 145 if err := mount.Unmount(p.PropagatedMount); err != nil { 146 logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err) 147 } 148 } 149 150 if restart { 151 pm.enable(p, c, true) 152 } 153 } 154 155 return nil 156 } 157 158 func (pm *Manager) reload() error { // todo: restore 159 dir, err := ioutil.ReadDir(pm.config.Root) 160 if err != nil { 161 return errors.Wrapf(err, "failed to read %v", pm.config.Root) 162 } 163 plugins := make(map[string]*v2.Plugin) 164 for _, v := range dir { 165 if validFullID.MatchString(v.Name()) { 166 p, err := pm.loadPlugin(v.Name()) 167 if err != nil { 168 return err 169 } 170 plugins[p.GetID()] = p 171 } 172 } 173 174 pm.config.Store.SetAll(plugins) 175 176 var wg sync.WaitGroup 177 wg.Add(len(plugins)) 178 for _, p := range plugins { 179 c := &controller{} // todo: remove this 180 pm.cMap[p] = c 181 go func(p *v2.Plugin) { 182 defer wg.Done() 183 if err := pm.restorePlugin(p); err != nil { 184 logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err) 185 return 186 } 187 188 if p.Rootfs != "" { 189 p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs") 190 } 191 192 // We should only enable rootfs propagation for certain plugin types that need it. 193 for _, typ := range p.PluginObj.Config.Interface.Types { 194 if (typ.Capability == "volumedriver" || typ.Capability == "graphdriver") && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") { 195 if p.PluginObj.Config.PropagatedMount != "" { 196 // TODO: sanitize PropagatedMount and prevent breakout 197 p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) 198 if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil { 199 logrus.Errorf("failed to create PropagatedMount directory at %s: %v", p.PropagatedMount, err) 200 return 201 } 202 } 203 } 204 } 205 206 pm.save(p) 207 requiresManualRestore := !pm.config.LiveRestoreEnabled && p.IsEnabled() 208 209 if requiresManualRestore { 210 // if liveRestore is not enabled, the plugin will be stopped now so we should enable it 211 if err := pm.enable(p, c, true); err != nil { 212 logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err) 213 } 214 } 215 }(p) 216 } 217 wg.Wait() 218 return nil 219 } 220 221 func (pm *Manager) loadPlugin(id string) (*v2.Plugin, error) { 222 p := filepath.Join(pm.config.Root, id, configFileName) 223 dt, err := ioutil.ReadFile(p) 224 if err != nil { 225 return nil, errors.Wrapf(err, "error reading %v", p) 226 } 227 var plugin v2.Plugin 228 if err := json.Unmarshal(dt, &plugin); err != nil { 229 return nil, errors.Wrapf(err, "error decoding %v", p) 230 } 231 return &plugin, nil 232 } 233 234 func (pm *Manager) save(p *v2.Plugin) error { 235 pluginJSON, err := json.Marshal(p) 236 if err != nil { 237 return errors.Wrap(err, "failed to marshal plugin json") 238 } 239 if err := ioutils.AtomicWriteFile(filepath.Join(pm.config.Root, p.GetID(), configFileName), pluginJSON, 0600); err != nil { 240 return err 241 } 242 return nil 243 } 244 245 // GC cleans up unrefrenced blobs. This is recommended to run in a goroutine 246 func (pm *Manager) GC() { 247 pm.muGC.Lock() 248 defer pm.muGC.Unlock() 249 250 whitelist := make(map[digest.Digest]struct{}) 251 for _, p := range pm.config.Store.GetAll() { 252 whitelist[p.Config] = struct{}{} 253 for _, b := range p.Blobsums { 254 whitelist[b] = struct{}{} 255 } 256 } 257 258 pm.blobStore.gc(whitelist) 259 } 260 261 type logHook struct{ id string } 262 263 func (logHook) Levels() []logrus.Level { 264 return logrus.AllLevels 265 } 266 267 func (l logHook) Fire(entry *logrus.Entry) error { 268 entry.Data = logrus.Fields{"plugin": l.id} 269 return nil 270 } 271 272 func attachToLog(id string) func(libcontainerd.IOPipe) error { 273 return func(iop libcontainerd.IOPipe) error { 274 iop.Stdin.Close() 275 276 logger := logrus.New() 277 logger.Hooks.Add(logHook{id}) 278 // TODO: cache writer per id 279 w := logger.Writer() 280 go func() { 281 io.Copy(w, iop.Stdout) 282 }() 283 go func() { 284 // TODO: update logrus and use logger.WriterLevel 285 io.Copy(w, iop.Stderr) 286 }() 287 return nil 288 } 289 } 290 291 func validatePrivileges(requiredPrivileges, privileges types.PluginPrivileges) error { 292 // todo: make a better function that doesn't check order 293 if !reflect.DeepEqual(privileges, requiredPrivileges) { 294 return errors.New("incorrect privileges") 295 } 296 return nil 297 } 298 299 func configToRootFS(c []byte) (*image.RootFS, error) { 300 var pluginConfig types.PluginConfig 301 if err := json.Unmarshal(c, &pluginConfig); err != nil { 302 return nil, err 303 } 304 // validation for empty rootfs is in distribution code 305 if pluginConfig.Rootfs == nil { 306 return nil, nil 307 } 308 309 return rootFSFromPlugin(pluginConfig.Rootfs), nil 310 } 311 312 func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS { 313 rootFS := image.RootFS{ 314 Type: pluginfs.Type, 315 DiffIDs: make([]layer.DiffID, len(pluginfs.DiffIds)), 316 } 317 for i := range pluginfs.DiffIds { 318 rootFS.DiffIDs[i] = layer.DiffID(pluginfs.DiffIds[i]) 319 } 320 321 return &rootFS 322 }