github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 "testing" 9 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/pkg/stringid" 12 "github.com/docker/docker/pkg/system" 13 v2 "github.com/docker/docker/plugin/v2" 14 "github.com/moby/sys/mount" 15 "github.com/moby/sys/mountinfo" 16 specs "github.com/opencontainers/runtime-spec/specs-go" 17 "github.com/pkg/errors" 18 "gotest.tools/v3/skip" 19 ) 20 21 func TestManagerWithPluginMounts(t *testing.T) { 22 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 23 root, err := os.MkdirTemp("", "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 := mountinfo.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 := os.MkdirTemp("", "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 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 178 t.Parallel() 179 180 root, err := os.MkdirTemp("", 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 = os.MkdirTemp("", "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 }