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 }