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