gopkg.in/docker/docker.v23@v23.0.11/pkg/plugins/client_test.go (about) 1 package plugins // import "github.com/docker/docker/pkg/plugins" 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/docker/docker/pkg/plugins/transport" 16 "github.com/docker/go-connections/tlsconfig" 17 "gotest.tools/v3/assert" 18 is "gotest.tools/v3/assert/cmp" 19 ) 20 21 var ( 22 mux *http.ServeMux 23 server *httptest.Server 24 ) 25 26 func setupRemotePluginServer() string { 27 mux = http.NewServeMux() 28 server = httptest.NewServer(mux) 29 return server.URL 30 } 31 32 func teardownRemotePluginServer() { 33 if server != nil { 34 server.Close() 35 } 36 } 37 38 func TestFailedConnection(t *testing.T) { 39 c, _ := NewClient("tcp://127.0.0.1:1", &tlsconfig.Options{InsecureSkipVerify: true}) 40 _, err := c.callWithRetry("Service.Method", nil, false) 41 if err == nil { 42 t.Fatal("Unexpected successful connection") 43 } 44 } 45 46 func TestFailOnce(t *testing.T) { 47 addr := setupRemotePluginServer() 48 defer teardownRemotePluginServer() 49 50 failed := false 51 mux.HandleFunc("/Test.FailOnce", func(w http.ResponseWriter, r *http.Request) { 52 if !failed { 53 failed = true 54 panic("Plugin not ready") 55 } 56 }) 57 58 c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true}) 59 b := strings.NewReader("body") 60 _, err := c.callWithRetry("Test.FailOnce", b, true) 61 if err != nil { 62 t.Fatal(err) 63 } 64 } 65 66 func TestEchoInputOutput(t *testing.T) { 67 addr := setupRemotePluginServer() 68 defer teardownRemotePluginServer() 69 70 m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}} 71 72 mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) { 73 if r.Method != http.MethodPost { 74 t.Fatalf("Expected POST, got %s\n", r.Method) 75 } 76 77 header := w.Header() 78 header.Set("Content-Type", transport.VersionMimetype) 79 80 io.Copy(w, r.Body) 81 }) 82 83 c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true}) 84 var output Manifest 85 err := c.Call("Test.Echo", m, &output) 86 if err != nil { 87 t.Fatal(err) 88 } 89 90 assert.Check(t, is.DeepEqual(m, output)) 91 err = c.Call("Test.Echo", nil, nil) 92 if err != nil { 93 t.Fatal(err) 94 } 95 } 96 97 func TestBackoff(t *testing.T) { 98 cases := []struct { 99 retries int 100 expTimeOff time.Duration 101 }{ 102 {0, time.Duration(1)}, 103 {1, time.Duration(2)}, 104 {2, time.Duration(4)}, 105 {4, time.Duration(16)}, 106 {6, time.Duration(30)}, 107 {10, time.Duration(30)}, 108 } 109 110 for _, c := range cases { 111 s := c.expTimeOff * time.Second 112 if d := backoff(c.retries); d != s { 113 t.Fatalf("Retry %v, expected %v, was %v\n", c.retries, s, d) 114 } 115 } 116 } 117 118 func TestAbortRetry(t *testing.T) { 119 cases := []struct { 120 timeOff time.Duration 121 expAbort bool 122 }{ 123 {time.Duration(1), false}, 124 {time.Duration(2), false}, 125 {time.Duration(10), false}, 126 {time.Duration(30), true}, 127 {time.Duration(40), true}, 128 } 129 130 for _, c := range cases { 131 s := c.timeOff * time.Second 132 if a := abort(time.Now(), s); a != c.expAbort { 133 t.Fatalf("Duration %v, expected %v, was %v\n", c.timeOff, s, a) 134 } 135 } 136 } 137 138 func TestClientScheme(t *testing.T) { 139 cases := map[string]string{ 140 "tcp://127.0.0.1:8080": "http", 141 "unix:///usr/local/plugins/foo": "http", 142 "http://127.0.0.1:8080": "http", 143 "https://127.0.0.1:8080": "https", 144 } 145 146 for addr, scheme := range cases { 147 u, err := url.Parse(addr) 148 if err != nil { 149 t.Fatal(err) 150 } 151 s := httpScheme(u) 152 153 if s != scheme { 154 t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, s) 155 } 156 } 157 } 158 159 func TestNewClientWithTimeout(t *testing.T) { 160 addr := setupRemotePluginServer() 161 defer teardownRemotePluginServer() 162 163 m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}} 164 165 mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) { 166 time.Sleep(time.Duration(600) * time.Millisecond) 167 io.Copy(w, r.Body) 168 }) 169 170 // setting timeout of 500ms 171 timeout := time.Duration(500) * time.Millisecond 172 c, _ := NewClientWithTimeout(addr, &tlsconfig.Options{InsecureSkipVerify: true}, timeout) 173 var output Manifest 174 err := c.Call("Test.Echo", m, &output) 175 if err == nil { 176 t.Fatal("Expected timeout error") 177 } 178 } 179 180 func TestClientStream(t *testing.T) { 181 addr := setupRemotePluginServer() 182 defer teardownRemotePluginServer() 183 184 m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}} 185 var output Manifest 186 187 mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) { 188 if r.Method != http.MethodPost { 189 t.Fatalf("Expected POST, got %s", r.Method) 190 } 191 192 header := w.Header() 193 header.Set("Content-Type", transport.VersionMimetype) 194 195 io.Copy(w, r.Body) 196 }) 197 198 c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true}) 199 body, err := c.Stream("Test.Echo", m) 200 if err != nil { 201 t.Fatal(err) 202 } 203 defer body.Close() 204 if err := json.NewDecoder(body).Decode(&output); err != nil { 205 t.Fatalf("Test.Echo: error reading plugin resp: %v", err) 206 } 207 assert.Check(t, is.DeepEqual(m, output)) 208 } 209 210 func TestClientSendFile(t *testing.T) { 211 addr := setupRemotePluginServer() 212 defer teardownRemotePluginServer() 213 214 m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}} 215 var output Manifest 216 var buf bytes.Buffer 217 if err := json.NewEncoder(&buf).Encode(m); err != nil { 218 t.Fatal(err) 219 } 220 mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) { 221 if r.Method != http.MethodPost { 222 t.Fatalf("Expected POST, got %s\n", r.Method) 223 } 224 225 header := w.Header() 226 header.Set("Content-Type", transport.VersionMimetype) 227 228 io.Copy(w, r.Body) 229 }) 230 231 c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true}) 232 if err := c.SendFile("Test.Echo", &buf, &output); err != nil { 233 t.Fatal(err) 234 } 235 assert.Check(t, is.DeepEqual(m, output)) 236 } 237 238 func TestClientWithRequestTimeout(t *testing.T) { 239 type timeoutError interface { 240 Timeout() bool 241 } 242 243 unblock := make(chan struct{}) 244 testHandler := func(w http.ResponseWriter, r *http.Request) { 245 select { 246 case <-unblock: 247 case <-r.Context().Done(): 248 } 249 w.WriteHeader(http.StatusOK) 250 } 251 252 srv := httptest.NewServer(http.HandlerFunc(testHandler)) 253 defer func() { 254 close(unblock) 255 srv.Close() 256 }() 257 258 client := &Client{http: srv.Client(), requestFactory: &testRequestWrapper{srv}} 259 errCh := make(chan error, 1) 260 go func() { 261 _, err := client.callWithRetry("/Plugin.Hello", nil, false, WithRequestTimeout(time.Millisecond)) 262 errCh <- err 263 }() 264 265 timer := time.NewTimer(5 * time.Second) 266 defer timer.Stop() 267 select { 268 case err := <-errCh: 269 var tErr timeoutError 270 if assert.Check(t, errors.As(err, &tErr), "want timeout error, got %T", err) { 271 assert.Check(t, tErr.Timeout()) 272 } 273 case <-timer.C: 274 t.Fatal("client request did not time out in time") 275 } 276 } 277 278 type testRequestWrapper struct { 279 *httptest.Server 280 } 281 282 func (w *testRequestWrapper) NewRequest(path string, data io.Reader) (*http.Request, error) { 283 req, err := http.NewRequest(http.MethodPost, path, data) 284 if err != nil { 285 return nil, err 286 } 287 u, err := url.Parse(w.Server.URL) 288 if err != nil { 289 return nil, err 290 } 291 req.URL = u 292 return req, nil 293 }