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 }