test
[platform/upstream/docker-engine.git] / registry / registry_mock_test.go
1 // +build !solaris
2
3 package registry
4
5 import (
6         "encoding/json"
7         "errors"
8         "fmt"
9         "io"
10         "io/ioutil"
11         "net"
12         "net/http"
13         "net/http/httptest"
14         "net/url"
15         "strconv"
16         "strings"
17         "testing"
18         "time"
19
20         "github.com/docker/distribution/reference"
21         registrytypes "github.com/docker/docker/api/types/registry"
22         "github.com/gorilla/mux"
23
24         "github.com/Sirupsen/logrus"
25 )
26
27 var (
28         testHTTPServer  *httptest.Server
29         testHTTPSServer *httptest.Server
30         testLayers      = map[string]map[string]string{
31                 "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
32                         "json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
33                                 "comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00",
34                                 "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
35                                 "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
36                                 "Tty":false,"OpenStdin":false,"StdinOnce":false,
37                                 "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
38                                 "VolumesFrom":"","Entrypoint":null},"Size":424242}`,
39                         "checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
40                         "checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c",
41                         "ancestry":        `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
42                         "layer": string([]byte{
43                                 0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
44                                 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
45                                 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66,
46                                 0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78,
47                                 0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31,
48                                 0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8,
49                                 0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1,
50                                 0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6,
51                                 0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb,
52                                 0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce,
53                                 0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00,
54                         }),
55                 },
56                 "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": {
57                         "json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
58                                 "parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
59                                 "comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00",
60                                 "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0,
61                                 "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,
62                                 "Tty":false,"OpenStdin":false,"StdinOnce":false,
63                                 "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null,
64                                 "VolumesFrom":"","Entrypoint":null},"Size":424242}`,
65                         "checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2",
66                         "checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2",
67                         "ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
68                                 "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`,
69                         "layer": string([]byte{
70                                 0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65,
71                                 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05,
72                                 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56,
73                                 0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5,
74                                 0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e,
75                                 0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93,
76                                 0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee,
77                                 0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9,
78                                 0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55,
79                                 0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17,
80                                 0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00,
81                         }),
82                 },
83         }
84         testRepositories = map[string]map[string]string{
85                 "foo42/bar": {
86                         "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
87                         "test":   "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
88                 },
89         }
90         mockHosts = map[string][]net.IP{
91                 "":            {net.ParseIP("0.0.0.0")},
92                 "localhost":   {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
93                 "example.com": {net.ParseIP("42.42.42.42")},
94                 "other.com":   {net.ParseIP("43.43.43.43")},
95         }
96 )
97
98 func init() {
99         r := mux.NewRouter()
100
101         // /v1/
102         r.HandleFunc("/v1/_ping", handlerGetPing).Methods("GET")
103         r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods("GET")
104         r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods("PUT")
105         r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods("GET", "DELETE")
106         r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods("GET")
107         r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods("PUT")
108         r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods("GET", "POST", "PUT")
109         r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods("GET", "PUT", "DELETE")
110         r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT")
111         r.HandleFunc("/v1/search", handlerSearch).Methods("GET")
112
113         // /v2/
114         r.HandleFunc("/v2/version", handlerGetPing).Methods("GET")
115
116         testHTTPServer = httptest.NewServer(handlerAccessLog(r))
117         testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r))
118
119         // override net.LookupIP
120         lookupIP = func(host string) ([]net.IP, error) {
121                 if host == "127.0.0.1" {
122                         // I believe in future Go versions this will fail, so let's fix it later
123                         return net.LookupIP(host)
124                 }
125                 for h, addrs := range mockHosts {
126                         if host == h {
127                                 return addrs, nil
128                         }
129                         for _, addr := range addrs {
130                                 if addr.String() == host {
131                                         return []net.IP{addr}, nil
132                                 }
133                         }
134                 }
135                 return nil, errors.New("lookup: no such host")
136         }
137 }
138
139 func handlerAccessLog(handler http.Handler) http.Handler {
140         logHandler := func(w http.ResponseWriter, r *http.Request) {
141                 logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL)
142                 handler.ServeHTTP(w, r)
143         }
144         return http.HandlerFunc(logHandler)
145 }
146
147 func makeURL(req string) string {
148         return testHTTPServer.URL + req
149 }
150
151 func makeHTTPSURL(req string) string {
152         return testHTTPSServer.URL + req
153 }
154
155 func makeIndex(req string) *registrytypes.IndexInfo {
156         index := &registrytypes.IndexInfo{
157                 Name: makeURL(req),
158         }
159         return index
160 }
161
162 func makeHTTPSIndex(req string) *registrytypes.IndexInfo {
163         index := &registrytypes.IndexInfo{
164                 Name: makeHTTPSURL(req),
165         }
166         return index
167 }
168
169 func makePublicIndex() *registrytypes.IndexInfo {
170         index := &registrytypes.IndexInfo{
171                 Name:     IndexServer,
172                 Secure:   true,
173                 Official: true,
174         }
175         return index
176 }
177
178 func makeServiceConfig(mirrors []string, insecureRegistries []string) *serviceConfig {
179         options := ServiceOptions{
180                 Mirrors:            mirrors,
181                 InsecureRegistries: insecureRegistries,
182         }
183
184         return newServiceConfig(options)
185 }
186
187 func writeHeaders(w http.ResponseWriter) {
188         h := w.Header()
189         h.Add("Server", "docker-tests/mock")
190         h.Add("Expires", "-1")
191         h.Add("Content-Type", "application/json")
192         h.Add("Pragma", "no-cache")
193         h.Add("Cache-Control", "no-cache")
194         h.Add("X-Docker-Registry-Version", "0.0.0")
195         h.Add("X-Docker-Registry-Config", "mock")
196 }
197
198 func writeResponse(w http.ResponseWriter, message interface{}, code int) {
199         writeHeaders(w)
200         w.WriteHeader(code)
201         body, err := json.Marshal(message)
202         if err != nil {
203                 io.WriteString(w, err.Error())
204                 return
205         }
206         w.Write(body)
207 }
208
209 func readJSON(r *http.Request, dest interface{}) error {
210         body, err := ioutil.ReadAll(r.Body)
211         if err != nil {
212                 return err
213         }
214         return json.Unmarshal(body, dest)
215 }
216
217 func apiError(w http.ResponseWriter, message string, code int) {
218         body := map[string]string{
219                 "error": message,
220         }
221         writeResponse(w, body, code)
222 }
223
224 func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
225         if a == b {
226                 return
227         }
228         if len(message) == 0 {
229                 message = fmt.Sprintf("%v != %v", a, b)
230         }
231         t.Fatal(message)
232 }
233
234 func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
235         if a != b {
236                 return
237         }
238         if len(message) == 0 {
239                 message = fmt.Sprintf("%v == %v", a, b)
240         }
241         t.Fatal(message)
242 }
243
244 // Similar to assertEqual, but does not stop test
245 func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
246         if a == b {
247                 return
248         }
249         message := fmt.Sprintf("%v != %v", a, b)
250         if len(messagePrefix) != 0 {
251                 message = messagePrefix + ": " + message
252         }
253         t.Error(message)
254 }
255
256 // Similar to assertNotEqual, but does not stop test
257 func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
258         if a != b {
259                 return
260         }
261         message := fmt.Sprintf("%v == %v", a, b)
262         if len(messagePrefix) != 0 {
263                 message = messagePrefix + ": " + message
264         }
265         t.Error(message)
266 }
267
268 func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
269         writeCookie := func() {
270                 value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
271                 cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600}
272                 http.SetCookie(w, cookie)
273                 //FIXME(sam): this should be sent only on Index routes
274                 value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano())
275                 w.Header().Add("X-Docker-Token", value)
276         }
277         if len(r.Cookies()) > 0 {
278                 writeCookie()
279                 return true
280         }
281         if len(r.Header.Get("Authorization")) > 0 {
282                 writeCookie()
283                 return true
284         }
285         w.Header().Add("WWW-Authenticate", "token")
286         apiError(w, "Wrong auth", 401)
287         return false
288 }
289
290 func handlerGetPing(w http.ResponseWriter, r *http.Request) {
291         writeResponse(w, true, 200)
292 }
293
294 func handlerGetImage(w http.ResponseWriter, r *http.Request) {
295         if !requiresAuth(w, r) {
296                 return
297         }
298         vars := mux.Vars(r)
299         layer, exists := testLayers[vars["image_id"]]
300         if !exists {
301                 http.NotFound(w, r)
302                 return
303         }
304         writeHeaders(w)
305         layerSize := len(layer["layer"])
306         w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize))
307         io.WriteString(w, layer[vars["action"]])
308 }
309
310 func handlerPutImage(w http.ResponseWriter, r *http.Request) {
311         if !requiresAuth(w, r) {
312                 return
313         }
314         vars := mux.Vars(r)
315         imageID := vars["image_id"]
316         action := vars["action"]
317         layer, exists := testLayers[imageID]
318         if !exists {
319                 if action != "json" {
320                         http.NotFound(w, r)
321                         return
322                 }
323                 layer = make(map[string]string)
324                 testLayers[imageID] = layer
325         }
326         if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" {
327                 if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] {
328                         apiError(w, "Wrong checksum", 400)
329                         return
330                 }
331         }
332         body, err := ioutil.ReadAll(r.Body)
333         if err != nil {
334                 apiError(w, fmt.Sprintf("Error: %s", err), 500)
335                 return
336         }
337         layer[action] = string(body)
338         writeResponse(w, true, 200)
339 }
340
341 func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
342         if !requiresAuth(w, r) {
343                 return
344         }
345         repositoryName, err := reference.WithName(mux.Vars(r)["repository"])
346         if err != nil {
347                 apiError(w, "Could not parse repository", 400)
348                 return
349         }
350         tags, exists := testRepositories[repositoryName.String()]
351         if !exists {
352                 apiError(w, "Repository not found", 404)
353                 return
354         }
355         if r.Method == "DELETE" {
356                 delete(testRepositories, repositoryName.String())
357                 writeResponse(w, true, 200)
358                 return
359         }
360         writeResponse(w, tags, 200)
361 }
362
363 func handlerGetTag(w http.ResponseWriter, r *http.Request) {
364         if !requiresAuth(w, r) {
365                 return
366         }
367         vars := mux.Vars(r)
368         repositoryName, err := reference.WithName(vars["repository"])
369         if err != nil {
370                 apiError(w, "Could not parse repository", 400)
371                 return
372         }
373         tagName := vars["tag"]
374         tags, exists := testRepositories[repositoryName.String()]
375         if !exists {
376                 apiError(w, "Repository not found", 404)
377                 return
378         }
379         tag, exists := tags[tagName]
380         if !exists {
381                 apiError(w, "Tag not found", 404)
382                 return
383         }
384         writeResponse(w, tag, 200)
385 }
386
387 func handlerPutTag(w http.ResponseWriter, r *http.Request) {
388         if !requiresAuth(w, r) {
389                 return
390         }
391         vars := mux.Vars(r)
392         repositoryName, err := reference.WithName(vars["repository"])
393         if err != nil {
394                 apiError(w, "Could not parse repository", 400)
395                 return
396         }
397         tagName := vars["tag"]
398         tags, exists := testRepositories[repositoryName.String()]
399         if !exists {
400                 tags = make(map[string]string)
401                 testRepositories[repositoryName.String()] = tags
402         }
403         tagValue := ""
404         readJSON(r, tagValue)
405         tags[tagName] = tagValue
406         writeResponse(w, true, 200)
407 }
408
409 func handlerUsers(w http.ResponseWriter, r *http.Request) {
410         code := 200
411         if r.Method == "POST" {
412                 code = 201
413         } else if r.Method == "PUT" {
414                 code = 204
415         }
416         writeResponse(w, "", code)
417 }
418
419 func handlerImages(w http.ResponseWriter, r *http.Request) {
420         u, _ := url.Parse(testHTTPServer.URL)
421         w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s    ,  %s ", u.Host, "test.example.com"))
422         w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()))
423         if r.Method == "PUT" {
424                 if strings.HasSuffix(r.URL.Path, "images") {
425                         writeResponse(w, "", 204)
426                         return
427                 }
428                 writeResponse(w, "", 200)
429                 return
430         }
431         if r.Method == "DELETE" {
432                 writeResponse(w, "", 204)
433                 return
434         }
435         images := []map[string]string{}
436         for imageID, layer := range testLayers {
437                 image := make(map[string]string)
438                 image["id"] = imageID
439                 image["checksum"] = layer["checksum_tarsum"]
440                 image["Tag"] = "latest"
441                 images = append(images, image)
442         }
443         writeResponse(w, images, 200)
444 }
445
446 func handlerAuth(w http.ResponseWriter, r *http.Request) {
447         writeResponse(w, "OK", 200)
448 }
449
450 func handlerSearch(w http.ResponseWriter, r *http.Request) {
451         result := &registrytypes.SearchResults{
452                 Query:      "fakequery",
453                 NumResults: 1,
454                 Results:    []registrytypes.SearchResult{{Name: "fakeimage", StarCount: 42}},
455         }
456         writeResponse(w, result, 200)
457 }
458
459 func TestPing(t *testing.T) {
460         res, err := http.Get(makeURL("/v1/_ping"))
461         if err != nil {
462                 t.Fatal(err)
463         }
464         assertEqual(t, res.StatusCode, 200, "")
465         assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock",
466                 "This is not a Mocked Registry")
467 }
468
469 /* Uncomment this to test Mocked Registry locally with curl
470  * WARNING: Don't push on the repos uncommented, it'll block the tests
471  *
472 func TestWait(t *testing.T) {
473         logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL)
474         c := make(chan int)
475         <-c
476 }
477
478 //*/