github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/plugin/manager_linux_test.go (about) 1 package plugin // import "github.com/docker/docker/plugin" 2 3 import ( 4 "io" 5 "io/ioutil" 6 "net" 7 "os" 8 "path/filepath" 9 "testing" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/pkg/mount" 13 "github.com/docker/docker/pkg/stringid" 14 "github.com/docker/docker/pkg/system" 15 "github.com/docker/docker/plugin/v2" 16 "github.com/opencontainers/runtime-spec/specs-go" 17 "github.com/pkg/errors" 18 "gotest.tools/skip" 19 ) 20 21 func TestManagerWithPluginMounts(t *testing.T) { 22 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 23 root, err := ioutil.TempDir("", "test-store-with-plugin-mounts") 24 if err != nil { 25 t.Fatal(err) 26 } 27 defer system.EnsureRemoveAll(root) 28 29 s := NewStore() 30 managerRoot := filepath.Join(root, "manager") 31 p1 := newTestPlugin(t, "test1", "testcap", managerRoot) 32 33 p2 := newTestPlugin(t, "test2", "testcap", managerRoot) 34 p2.PluginObj.Enabled = true 35 36 m, err := NewManager( 37 ManagerConfig{ 38 Store: s, 39 Root: managerRoot, 40 ExecRoot: filepath.Join(root, "exec"), 41 CreateExecutor: func(*Manager) (Executor, error) { return nil, nil }, 42 LogPluginEvent: func(_, _, _ string) {}, 43 }) 44 if err != nil { 45 t.Fatal(err) 46 } 47 48 if err := s.Add(p1); err != nil { 49 t.Fatal(err) 50 } 51 if err := s.Add(p2); err != nil { 52 t.Fatal(err) 53 } 54 55 // Create a mount to simulate a plugin that has created it's own mounts 56 p2Mount := filepath.Join(p2.Rootfs, "testmount") 57 if err := os.MkdirAll(p2Mount, 0755); err != nil { 58 t.Fatal(err) 59 } 60 if err := mount.Mount("tmpfs", p2Mount, "tmpfs", ""); err != nil { 61 t.Fatal(err) 62 } 63 64 if err := m.Remove(p1.GetID(), &types.PluginRmConfig{ForceRemove: true}); err != nil { 65 t.Fatal(err) 66 } 67 if mounted, err := mount.Mounted(p2Mount); !mounted || err != nil { 68 t.Fatalf("expected %s to be mounted, err: %v", p2Mount, err) 69 } 70 } 71 72 func newTestPlugin(t *testing.T, name, cap, root string) *v2.Plugin { 73 id := stringid.GenerateRandomID() 74 rootfs := filepath.Join(root, id) 75 if err := os.MkdirAll(rootfs, 0755); err != nil { 76 t.Fatal(err) 77 } 78 79 p := v2.Plugin{PluginObj: types.Plugin{ID: id, Name: name}} 80 p.Rootfs = rootfs 81 iType := types.PluginInterfaceType{Capability: cap, Prefix: "docker", Version: "1.0"} 82 i := types.PluginConfigInterface{Socket: "plugin.sock", Types: []types.PluginInterfaceType{iType}} 83 p.PluginObj.Config.Interface = i 84 p.PluginObj.ID = id 85 86 return &p 87 } 88 89 type simpleExecutor struct { 90 } 91 92 func (e *simpleExecutor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { 93 return errors.New("Create failed") 94 } 95 96 func (e *simpleExecutor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) { 97 return false, nil 98 } 99 100 func (e *simpleExecutor) IsRunning(id string) (bool, error) { 101 return false, nil 102 } 103 104 func (e *simpleExecutor) Signal(id string, signal int) error { 105 return nil 106 } 107 108 func TestCreateFailed(t *testing.T) { 109 root, err := ioutil.TempDir("", "test-create-failed") 110 if err != nil { 111 t.Fatal(err) 112 } 113 defer system.EnsureRemoveAll(root) 114 115 s := NewStore() 116 managerRoot := filepath.Join(root, "manager") 117 p := newTestPlugin(t, "create", "testcreate", managerRoot) 118 119 m, err := NewManager( 120 ManagerConfig{ 121 Store: s, 122 Root: managerRoot, 123 ExecRoot: filepath.Join(root, "exec"), 124 CreateExecutor: func(*Manager) (Executor, error) { return &simpleExecutor{}, nil }, 125 LogPluginEvent: func(_, _, _ string) {}, 126 }) 127 if err != nil { 128 t.Fatal(err) 129 } 130 131 if err := s.Add(p); err != nil { 132 t.Fatal(err) 133 } 134 135 if err := m.enable(p, &controller{}, false); err == nil { 136 t.Fatalf("expected Create failed error, got %v", err) 137 } 138 139 if err := m.Remove(p.GetID(), &types.PluginRmConfig{ForceRemove: true}); err != nil { 140 t.Fatal(err) 141 } 142 } 143 144 type executorWithRunning struct { 145 m *Manager 146 root string 147 exitChans map[string]chan struct{} 148 } 149 150 func (e *executorWithRunning) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { 151 sockAddr := filepath.Join(e.root, id, "plugin.sock") 152 ch := make(chan struct{}) 153 if e.exitChans == nil { 154 e.exitChans = make(map[string]chan struct{}) 155 } 156 e.exitChans[id] = ch 157 listenTestPlugin(sockAddr, ch) 158 return nil 159 } 160 161 func (e *executorWithRunning) IsRunning(id string) (bool, error) { 162 return true, nil 163 } 164 func (e *executorWithRunning) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) { 165 return true, nil 166 } 167 168 func (e *executorWithRunning) Signal(id string, signal int) error { 169 ch := e.exitChans[id] 170 ch <- struct{}{} 171 <-ch 172 e.m.HandleExitEvent(id) 173 return nil 174 } 175 176 func TestPluginAlreadyRunningOnStartup(t *testing.T) { 177 t.Parallel() 178 179 root, err := ioutil.TempDir("", t.Name()) 180 if err != nil { 181 t.Fatal(err) 182 } 183 defer system.EnsureRemoveAll(root) 184 185 for _, test := range []struct { 186 desc string 187 config ManagerConfig 188 }{ 189 { 190 desc: "live-restore-disabled", 191 config: ManagerConfig{ 192 LogPluginEvent: func(_, _, _ string) {}, 193 }, 194 }, 195 { 196 desc: "live-restore-enabled", 197 config: ManagerConfig{ 198 LogPluginEvent: func(_, _, _ string) {}, 199 LiveRestoreEnabled: true, 200 }, 201 }, 202 } { 203 t.Run(test.desc, func(t *testing.T) { 204 config := test.config 205 desc := test.desc 206 t.Parallel() 207 208 p := newTestPlugin(t, desc, desc, config.Root) 209 p.PluginObj.Enabled = true 210 211 // Need a short-ish path here so we don't run into unix socket path length issues. 212 config.ExecRoot, err = ioutil.TempDir("", "plugintest") 213 214 executor := &executorWithRunning{root: config.ExecRoot} 215 config.CreateExecutor = func(m *Manager) (Executor, error) { executor.m = m; return executor, nil } 216 217 if err := executor.Create(p.GetID(), specs.Spec{}, nil, nil); err != nil { 218 t.Fatal(err) 219 } 220 221 root := filepath.Join(root, desc) 222 config.Root = filepath.Join(root, "manager") 223 if err := os.MkdirAll(filepath.Join(config.Root, p.GetID()), 0755); err != nil { 224 t.Fatal(err) 225 } 226 227 if !p.IsEnabled() { 228 t.Fatal("plugin should be enabled") 229 } 230 if err := (&Manager{config: config}).save(p); err != nil { 231 t.Fatal(err) 232 } 233 234 s := NewStore() 235 config.Store = s 236 if err != nil { 237 t.Fatal(err) 238 } 239 defer system.EnsureRemoveAll(config.ExecRoot) 240 241 m, err := NewManager(config) 242 if err != nil { 243 t.Fatal(err) 244 } 245 defer m.Shutdown() 246 247 p = s.GetAll()[p.GetID()] // refresh `p` with what the manager knows 248 if p.Client() == nil { 249 t.Fatal("plugin client should not be nil") 250 } 251 }) 252 } 253 } 254 255 func listenTestPlugin(sockAddr string, exit chan struct{}) (net.Listener, error) { 256 if err := os.MkdirAll(filepath.Dir(sockAddr), 0755); err != nil { 257 return nil, err 258 } 259 l, err := net.Listen("unix", sockAddr) 260 if err != nil { 261 return nil, err 262 } 263 go func() { 264 for { 265 conn, err := l.Accept() 266 if err != nil { 267 return 268 } 269 conn.Close() 270 } 271 }() 272 go func() { 273 <-exit 274 l.Close() 275 os.Remove(sockAddr) 276 exit <- struct{}{} 277 }() 278 return l, nil 279 }