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  }