github.com/demonoid81/containerd@v1.3.4/remotes/docker/fetcher_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package docker
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"math/rand"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"net/url"
    29  	"testing"
    30  
    31  	"github.com/docker/distribution/registry/api/errcode"
    32  	"github.com/pkg/errors"
    33  	"gotest.tools/assert"
    34  )
    35  
    36  func TestFetcherOpen(t *testing.T) {
    37  	content := make([]byte, 128)
    38  	rand.New(rand.NewSource(1)).Read(content)
    39  	start := 0
    40  
    41  	s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
    42  		if start > 0 {
    43  			rw.Header().Set("content-range", fmt.Sprintf("bytes %d-127/128", start))
    44  		}
    45  		rw.Header().Set("content-length", fmt.Sprintf("%d", len(content[start:])))
    46  		rw.Write(content[start:])
    47  	}))
    48  	defer s.Close()
    49  
    50  	u, err := url.Parse(s.URL)
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	f := dockerFetcher{&dockerBase{
    56  		namespace: "nonempty",
    57  	}}
    58  
    59  	host := RegistryHost{
    60  		Client: s.Client(),
    61  		Host:   u.Host,
    62  		Scheme: u.Scheme,
    63  		Path:   u.Path,
    64  	}
    65  
    66  	ctx := context.Background()
    67  
    68  	req := f.request(host, http.MethodGet)
    69  
    70  	checkReader := func(o int64) {
    71  		t.Helper()
    72  
    73  		rc, err := f.open(ctx, req, "", o)
    74  		if err != nil {
    75  			t.Fatalf("failed to open: %+v", err)
    76  		}
    77  		b, err := ioutil.ReadAll(rc)
    78  		if err != nil {
    79  			t.Fatal(err)
    80  		}
    81  		expected := content[o:]
    82  		if len(b) != len(expected) {
    83  			t.Errorf("unexpected length %d, expected %d", len(b), len(expected))
    84  			return
    85  		}
    86  		for i, c := range expected {
    87  			if b[i] != c {
    88  				t.Errorf("unexpected byte %x at %d, expected %x", b[i], i, c)
    89  				return
    90  			}
    91  		}
    92  
    93  	}
    94  
    95  	checkReader(0)
    96  
    97  	// Test server ignores content range
    98  	checkReader(25)
    99  
   100  	// Use content range on server
   101  	start = 20
   102  	checkReader(20)
   103  
   104  	// Check returning just last byte and no bytes
   105  	start = 127
   106  	checkReader(127)
   107  	start = 128
   108  	checkReader(128)
   109  
   110  	// Check that server returning a different content range
   111  	// then requested errors
   112  	start = 30
   113  	_, err = f.open(ctx, req, "", 20)
   114  	if err == nil {
   115  		t.Fatal("expected error opening with invalid server response")
   116  	}
   117  }
   118  
   119  // New set of tests to test new error cases
   120  func TestDockerFetcherOpen(t *testing.T) {
   121  	tests := []struct {
   122  		name                   string
   123  		mockedStatus           int
   124  		mockedErr              error
   125  		want                   io.ReadCloser
   126  		wantErr                bool
   127  		wantServerMessageError bool
   128  		wantPlainError         bool
   129  		retries                int
   130  	}{
   131  		{
   132  			name:         "should return status and error.message if it exists if the registry request fails",
   133  			mockedStatus: 500,
   134  			mockedErr: errcode.Errors{errcode.Error{
   135  				Code:    errcode.ErrorCodeUnknown,
   136  				Message: "Test Error",
   137  			}},
   138  			want:                   nil,
   139  			wantErr:                true,
   140  			wantServerMessageError: true,
   141  		},
   142  		{
   143  			name:           "should return just status if the registry request fails and does not return a docker error",
   144  			mockedStatus:   500,
   145  			mockedErr:      fmt.Errorf("Non-docker error"),
   146  			want:           nil,
   147  			wantErr:        true,
   148  			wantPlainError: true,
   149  		}, {
   150  			name:           "should return StatusRequestTimeout after 5 retries",
   151  			mockedStatus:   http.StatusRequestTimeout,
   152  			mockedErr:      fmt.Errorf(http.StatusText(http.StatusRequestTimeout)),
   153  			want:           nil,
   154  			wantErr:        true,
   155  			wantPlainError: true,
   156  			retries:        5,
   157  		}, {
   158  			name:           "should return StatusTooManyRequests after 5 retries",
   159  			mockedStatus:   http.StatusTooManyRequests,
   160  			mockedErr:      fmt.Errorf(http.StatusText(http.StatusTooManyRequests)),
   161  			want:           nil,
   162  			wantErr:        true,
   163  			wantPlainError: true,
   164  			retries:        5,
   165  		},
   166  	}
   167  	for _, tt := range tests {
   168  		t.Run(tt.name, func(t *testing.T) {
   169  
   170  			s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
   171  				if tt.retries > 0 {
   172  					tt.retries--
   173  				}
   174  				rw.WriteHeader(tt.mockedStatus)
   175  				bytes, _ := json.Marshal(tt.mockedErr)
   176  				rw.Write(bytes)
   177  			}))
   178  			defer s.Close()
   179  
   180  			u, err := url.Parse(s.URL)
   181  			if err != nil {
   182  				t.Fatal(err)
   183  			}
   184  
   185  			f := dockerFetcher{&dockerBase{
   186  				namespace: "ns",
   187  			}}
   188  
   189  			host := RegistryHost{
   190  				Client: s.Client(),
   191  				Host:   u.Host,
   192  				Scheme: u.Scheme,
   193  				Path:   u.Path,
   194  			}
   195  
   196  			req := f.request(host, http.MethodGet)
   197  
   198  			got, err := f.open(context.TODO(), req, "", 0)
   199  			assert.Equal(t, tt.wantErr, (err != nil))
   200  			assert.Equal(t, tt.want, got)
   201  			assert.Equal(t, tt.retries, 0)
   202  			if tt.wantErr {
   203  				var expectedError error
   204  				if tt.wantServerMessageError {
   205  					expectedError = errors.Errorf("unexpected status code %v/ns: %v %s - Server message: %s", s.URL, tt.mockedStatus, http.StatusText(tt.mockedStatus), tt.mockedErr.Error())
   206  				} else if tt.wantPlainError {
   207  					expectedError = errors.Errorf("unexpected status code %v/ns: %v %s", s.URL, tt.mockedStatus, http.StatusText(tt.mockedStatus))
   208  				}
   209  				assert.Equal(t, expectedError.Error(), err.Error())
   210  
   211  			}
   212  
   213  		})
   214  	}
   215  }