github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/plugin/manager_linux_test.go (about) 1 package plugin // import "github.com/docker/docker/plugin" 2 3 import ( 4 "io" 5 "net" 6 "os" 7 "path/filepath" 8 "syscall" 9 "testing" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/pkg/containerfs" 13 "github.com/docker/docker/pkg/stringid" 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 := os.MkdirTemp("", "test-store-with-plugin-mounts") 25 if err != nil { 26 t.Fatal(err) 27 } 28 defer containerfs.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 Executor 92 } 93 94 func (e *simpleExecutor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { 95 return errors.New("Create failed") 96 } 97 98 func TestCreateFailed(t *testing.T) { 99 root, err := os.MkdirTemp("", "test-create-failed") 100 if err != nil { 101 t.Fatal(err) 102 } 103 defer containerfs.EnsureRemoveAll(root) 104 105 s := NewStore() 106 managerRoot := filepath.Join(root, "manager") 107 p := newTestPlugin(t, "create", "testcreate", managerRoot) 108 109 m, err := NewManager( 110 ManagerConfig{ 111 Store: s, 112 Root: managerRoot, 113 ExecRoot: filepath.Join(root, "exec"), 114 CreateExecutor: func(*Manager) (Executor, error) { return &simpleExecutor{}, nil }, 115 LogPluginEvent: func(_, _, _ string) {}, 116 }) 117 if err != nil { 118 t.Fatal(err) 119 } 120 121 if err := s.Add(p); err != nil { 122 t.Fatal(err) 123 } 124 125 if err := m.enable(p, &controller{}, false); err == nil { 126 t.Fatalf("expected Create failed error, got %v", err) 127 } 128 129 if err := m.Remove(p.GetID(), &types.PluginRmConfig{ForceRemove: true}); err != nil { 130 t.Fatal(err) 131 } 132 } 133 134 type executorWithRunning struct { 135 m *Manager 136 root string 137 exitChans map[string]chan struct{} 138 } 139 140 func (e *executorWithRunning) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { 141 sockAddr := filepath.Join(e.root, id, "plugin.sock") 142 ch := make(chan struct{}) 143 if e.exitChans == nil { 144 e.exitChans = make(map[string]chan struct{}) 145 } 146 e.exitChans[id] = ch 147 listenTestPlugin(sockAddr, ch) 148 return nil 149 } 150 151 func (e *executorWithRunning) IsRunning(id string) (bool, error) { 152 return true, nil 153 } 154 func (e *executorWithRunning) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) { 155 return true, nil 156 } 157 158 func (e *executorWithRunning) Signal(id string, signal syscall.Signal) error { 159 ch := e.exitChans[id] 160 ch <- struct{}{} 161 <-ch 162 e.m.HandleExitEvent(id) 163 return nil 164 } 165 166 func TestPluginAlreadyRunningOnStartup(t *testing.T) { 167 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 168 t.Parallel() 169 170 root, err := os.MkdirTemp("", t.Name()) 171 if err != nil { 172 t.Fatal(err) 173 } 174 defer containerfs.EnsureRemoveAll(root) 175 176 for _, test := range []struct { 177 desc string 178 config ManagerConfig 179 }{ 180 { 181 desc: "live-restore-disabled", 182 config: ManagerConfig{ 183 LogPluginEvent: func(_, _, _ string) {}, 184 }, 185 }, 186 { 187 desc: "live-restore-enabled", 188 config: ManagerConfig{ 189 LogPluginEvent: func(_, _, _ string) {}, 190 LiveRestoreEnabled: true, 191 }, 192 }, 193 } { 194 t.Run(test.desc, func(t *testing.T) { 195 config := test.config 196 desc := test.desc 197 t.Parallel() 198 199 p := newTestPlugin(t, desc, desc, config.Root) 200 p.PluginObj.Enabled = true 201 202 // Need a short-ish path here so we don't run into unix socket path length issues. 203 config.ExecRoot, err = os.MkdirTemp("", "plugintest") 204 205 executor := &executorWithRunning{root: config.ExecRoot} 206 config.CreateExecutor = func(m *Manager) (Executor, error) { executor.m = m; return executor, nil } 207 208 if err := executor.Create(p.GetID(), specs.Spec{}, nil, nil); err != nil { 209 t.Fatal(err) 210 } 211 212 root := filepath.Join(root, desc) 213 config.Root = filepath.Join(root, "manager") 214 if err := os.MkdirAll(filepath.Join(config.Root, p.GetID()), 0755); err != nil { 215 t.Fatal(err) 216 } 217 218 if !p.IsEnabled() { 219 t.Fatal("plugin should be enabled") 220 } 221 if err := (&Manager{config: config}).save(p); err != nil { 222 t.Fatal(err) 223 } 224 225 s := NewStore() 226 config.Store = s 227 if err != nil { 228 t.Fatal(err) 229 } 230 defer containerfs.EnsureRemoveAll(config.ExecRoot) 231 232 m, err := NewManager(config) 233 if err != nil { 234 t.Fatal(err) 235 } 236 defer m.Shutdown() 237 238 p = s.GetAll()[p.GetID()] // refresh `p` with what the manager knows 239 if p.Client() == nil { 240 t.Fatal("plugin client should not be nil") 241 } 242 }) 243 } 244 } 245 246 func listenTestPlugin(sockAddr string, exit chan struct{}) (net.Listener, error) { 247 if err := os.MkdirAll(filepath.Dir(sockAddr), 0755); err != nil { 248 return nil, err 249 } 250 l, err := net.Listen("unix", sockAddr) 251 if err != nil { 252 return nil, err 253 } 254 go func() { 255 for { 256 conn, err := l.Accept() 257 if err != nil { 258 return 259 } 260 conn.Close() 261 } 262 }() 263 go func() { 264 <-exit 265 l.Close() 266 os.Remove(sockAddr) 267 exit <- struct{}{} 268 }() 269 return l, nil 270 }