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  }