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