github.com/wozhu6104/docker@v20.10.10+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 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 179 t.Parallel() 180 181 root, err := ioutil.TempDir("", t.Name()) 182 if err != nil { 183 t.Fatal(err) 184 } 185 defer system.EnsureRemoveAll(root) 186 187 for _, test := range []struct { 188 desc string 189 config ManagerConfig 190 }{ 191 { 192 desc: "live-restore-disabled", 193 config: ManagerConfig{ 194 LogPluginEvent: func(_, _, _ string) {}, 195 }, 196 }, 197 { 198 desc: "live-restore-enabled", 199 config: ManagerConfig{ 200 LogPluginEvent: func(_, _, _ string) {}, 201 LiveRestoreEnabled: true, 202 }, 203 }, 204 } { 205 t.Run(test.desc, func(t *testing.T) { 206 config := test.config 207 desc := test.desc 208 t.Parallel() 209 210 p := newTestPlugin(t, desc, desc, config.Root) 211 p.PluginObj.Enabled = true 212 213 // Need a short-ish path here so we don't run into unix socket path length issues. 214 config.ExecRoot, err = ioutil.TempDir("", "plugintest") 215 216 executor := &executorWithRunning{root: config.ExecRoot} 217 config.CreateExecutor = func(m *Manager) (Executor, error) { executor.m = m; return executor, nil } 218 219 if err := executor.Create(p.GetID(), specs.Spec{}, nil, nil); err != nil { 220 t.Fatal(err) 221 } 222 223 root := filepath.Join(root, desc) 224 config.Root = filepath.Join(root, "manager") 225 if err := os.MkdirAll(filepath.Join(config.Root, p.GetID()), 0755); err != nil { 226 t.Fatal(err) 227 } 228 229 if !p.IsEnabled() { 230 t.Fatal("plugin should be enabled") 231 } 232 if err := (&Manager{config: config}).save(p); err != nil { 233 t.Fatal(err) 234 } 235 236 s := NewStore() 237 config.Store = s 238 if err != nil { 239 t.Fatal(err) 240 } 241 defer system.EnsureRemoveAll(config.ExecRoot) 242 243 m, err := NewManager(config) 244 if err != nil { 245 t.Fatal(err) 246 } 247 defer m.Shutdown() 248 249 p = s.GetAll()[p.GetID()] // refresh `p` with what the manager knows 250 if p.Client() == nil { 251 t.Fatal("plugin client should not be nil") 252 } 253 }) 254 } 255 } 256 257 func listenTestPlugin(sockAddr string, exit chan struct{}) (net.Listener, error) { 258 if err := os.MkdirAll(filepath.Dir(sockAddr), 0755); err != nil { 259 return nil, err 260 } 261 l, err := net.Listen("unix", sockAddr) 262 if err != nil { 263 return nil, err 264 } 265 go func() { 266 for { 267 conn, err := l.Accept() 268 if err != nil { 269 return 270 } 271 conn.Close() 272 } 273 }() 274 go func() { 275 <-exit 276 l.Close() 277 os.Remove(sockAddr) 278 exit <- struct{}{} 279 }() 280 return l, nil 281 }