github.com/lalkh/containerd@v1.4.3/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/pkg/errors"
    32  	"gotest.tools/v3/assert"
    33  )
    34  
    35  func TestFetcherOpen(t *testing.T) {
    36  	content := make([]byte, 128)
    37  	rand.New(rand.NewSource(1)).Read(content)
    38  	start := 0
    39  
    40  	s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
    41  		if start > 0 {
    42  			rw.Header().Set("content-range", fmt.Sprintf("bytes %d-127/128", start))
    43  		}
    44  		rw.Header().Set("content-length", fmt.Sprintf("%d", len(content[start:])))
    45  		rw.Write(content[start:])
    46  	}))
    47  	defer s.Close()
    48  
    49  	u, err := url.Parse(s.URL)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  
    54  	f := dockerFetcher{&dockerBase{
    55  		repository: "nonempty",
    56  	}}
    57  
    58  	host := RegistryHost{
    59  		Client: s.Client(),
    60  		Host:   u.Host,
    61  		Scheme: u.Scheme,
    62  		Path:   u.Path,
    63  	}
    64  
    65  	ctx := context.Background()
    66  
    67  	req := f.request(host, http.MethodGet)
    68  
    69  	checkReader := func(o int64) {
    70  		t.Helper()
    71  
    72  		rc, err := f.open(ctx, req, "", o)
    73  		if err != nil {
    74  			t.Fatalf("failed to open: %+v", err)
    75  		}
    76  		b, err := ioutil.ReadAll(rc)
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  		expected := content[o:]
    81  		if len(b) != len(expected) {
    82  			t.Errorf("unexpected length %d, expected %d", len(b), len(expected))
    83  			return
    84  		}
    85  		for i, c := range expected {
    86  			if b[i] != c {
    87  				t.Errorf("unexpected byte %x at %d, expected %x", b[i], i, c)
    88  				return
    89  			}
    90  		}
    91  
    92  	}
    93  
    94  	checkReader(0)
    95  
    96  	// Test server ignores content range
    97  	checkReader(25)
    98  
    99  	// Use content range on server
   100  	start = 20
   101  	checkReader(20)
   102  
   103  	// Check returning just last byte and no bytes
   104  	start = 127
   105  	checkReader(127)
   106  	start = 128
   107  	checkReader(128)
   108  
   109  	// Check that server returning a different content range
   110  	// then requested errors
   111  	start = 30
   112  	_, err = f.open(ctx, req, "", 20)
   113  	if err == nil {
   114  		t.Fatal("expected error opening with invalid server response")
   115  	}
   116  }
   117  
   118  // New set of tests to test new error cases
   119  func TestDockerFetcherOpen(t *testing.T) {
   120  	tests := []struct {
   121  		name                   string
   122  		mockedStatus           int
   123  		mockedErr              error
   124  		want                   io.ReadCloser
   125  		wantErr                bool
   126  		wantServerMessageError bool
   127  		wantPlainError         bool
   128  		retries                int
   129  	}{
   130  		{
   131  			name:         "should return status and error.message if it exists if the registry request fails",
   132  			mockedStatus: 500,
   133  			mockedErr: Errors{Error{
   134  				Code:    ErrorCodeUnknown,
   135  				Message: "Test Error",
   136  			}},
   137  			want:                   nil,
   138  			wantErr:                true,
   139  			wantServerMessageError: true,
   140  		},
   141  		{
   142  			name:           "should return just status if the registry request fails and does not return a docker error",
   143  			mockedStatus:   500,
   144  			mockedErr:      fmt.Errorf("Non-docker error"),
   145  			want:           nil,
   146  			wantErr:        true,
   147  			wantPlainError: true,
   148  		}, {
   149  			name:           "should return StatusRequestTimeout after 5 retries",
   150  			mockedStatus:   http.StatusRequestTimeout,
   151  			mockedErr:      fmt.Errorf(http.StatusText(http.StatusRequestTimeout)),
   152  			want:           nil,
   153  			wantErr:        true,
   154  			wantPlainError: true,
   155  			retries:        5,
   156  		}, {
   157  			name:           "should return StatusTooManyRequests after 5 retries",
   158  			mockedStatus:   http.StatusTooManyRequests,
   159  			mockedErr:      fmt.Errorf(http.StatusText(http.StatusTooManyRequests)),
   160  			want:           nil,
   161  			wantErr:        true,
   162  			wantPlainError: true,
   163  			retries:        5,
   164  		},
   165  	}
   166  	for _, tt := range tests {
   167  		t.Run(tt.name, func(t *testing.T) {
   168  
   169  			s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
   170  				if tt.retries > 0 {
   171  					tt.retries--
   172  				}
   173  				rw.WriteHeader(tt.mockedStatus)
   174  				bytes, _ := json.Marshal(tt.mockedErr)
   175  				rw.Write(bytes)
   176  			}))
   177  			defer s.Close()
   178  
   179  			u, err := url.Parse(s.URL)
   180  			if err != nil {
   181  				t.Fatal(err)
   182  			}
   183  
   184  			f := dockerFetcher{&dockerBase{
   185  				repository: "ns",
   186  			}}
   187  
   188  			host := RegistryHost{
   189  				Client: s.Client(),
   190  				Host:   u.Host,
   191  				Scheme: u.Scheme,
   192  				Path:   u.Path,
   193  			}
   194  
   195  			req := f.request(host, http.MethodGet)
   196  
   197  			got, err := f.open(context.TODO(), req, "", 0)
   198  			assert.Equal(t, tt.wantErr, (err != nil))
   199  			assert.Equal(t, tt.want, got)
   200  			assert.Equal(t, tt.retries, 0)
   201  			if tt.wantErr {
   202  				var expectedError error
   203  				if tt.wantServerMessageError {
   204  					expectedError = errors.Errorf("unexpected status code %v/ns: %v %s - Server message: %s", s.URL, tt.mockedStatus, http.StatusText(tt.mockedStatus), tt.mockedErr.Error())
   205  				} else if tt.wantPlainError {
   206  					expectedError = errors.Errorf("unexpected status code %v/ns: %v %s", s.URL, tt.mockedStatus, http.StatusText(tt.mockedStatus))
   207  				}
   208  				assert.Equal(t, expectedError.Error(), err.Error())
   209  
   210  			}
   211  
   212  		})
   213  	}
   214  }