diff --git a/libnetwork/Godeps/Godeps.json b/libnetwork/Godeps/Godeps.json index ea90ab487b..5a0e638ceb 100644 --- a/libnetwork/Godeps/Godeps.json +++ b/libnetwork/Godeps/Godeps.json @@ -55,6 +55,14 @@ "Comment": "v2-3-g4160802", "Rev": "41608027bdce7bfa8959d653a00b954591220e67" }, + { + "ImportPath": "github.com/gorilla/context", + "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd" + }, + { + "ImportPath": "github.com/gorilla/mux", + "Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5" + }, { "ImportPath": "github.com/vishvananda/netlink", "Rev": "8eb64238879fed52fd51c5b30ad20b928fb4c36c" diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml new file mode 100644 index 0000000000..6796581fbd --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - 1.3 + - 1.4 + - tip diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/LICENSE b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/LICENSE new file mode 100644 index 0000000000..0e5fb87280 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Rodrigo Moraes. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/README.md b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/README.md new file mode 100644 index 0000000000..c60a31b053 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/README.md @@ -0,0 +1,7 @@ +context +======= +[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) + +gorilla/context is a general purpose registry for global request variables. + +Read the full documentation here: http://www.gorillatoolkit.org/pkg/context diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/context.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/context.go new file mode 100644 index 0000000000..81cb128b19 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/context.go @@ -0,0 +1,143 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "net/http" + "sync" + "time" +) + +var ( + mutex sync.RWMutex + data = make(map[*http.Request]map[interface{}]interface{}) + datat = make(map[*http.Request]int64) +) + +// Set stores a value for a given key in a given request. +func Set(r *http.Request, key, val interface{}) { + mutex.Lock() + if data[r] == nil { + data[r] = make(map[interface{}]interface{}) + datat[r] = time.Now().Unix() + } + data[r][key] = val + mutex.Unlock() +} + +// Get returns a value stored for a given key in a given request. +func Get(r *http.Request, key interface{}) interface{} { + mutex.RLock() + if ctx := data[r]; ctx != nil { + value := ctx[key] + mutex.RUnlock() + return value + } + mutex.RUnlock() + return nil +} + +// GetOk returns stored value and presence state like multi-value return of map access. +func GetOk(r *http.Request, key interface{}) (interface{}, bool) { + mutex.RLock() + if _, ok := data[r]; ok { + value, ok := data[r][key] + mutex.RUnlock() + return value, ok + } + mutex.RUnlock() + return nil, false +} + +// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. +func GetAll(r *http.Request) map[interface{}]interface{} { + mutex.RLock() + if context, ok := data[r]; ok { + result := make(map[interface{}]interface{}, len(context)) + for k, v := range context { + result[k] = v + } + mutex.RUnlock() + return result + } + mutex.RUnlock() + return nil +} + +// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if +// the request was registered. +func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { + mutex.RLock() + context, ok := data[r] + result := make(map[interface{}]interface{}, len(context)) + for k, v := range context { + result[k] = v + } + mutex.RUnlock() + return result, ok +} + +// Delete removes a value stored for a given key in a given request. +func Delete(r *http.Request, key interface{}) { + mutex.Lock() + if data[r] != nil { + delete(data[r], key) + } + mutex.Unlock() +} + +// Clear removes all values stored for a given request. +// +// This is usually called by a handler wrapper to clean up request +// variables at the end of a request lifetime. See ClearHandler(). +func Clear(r *http.Request) { + mutex.Lock() + clear(r) + mutex.Unlock() +} + +// clear is Clear without the lock. +func clear(r *http.Request) { + delete(data, r) + delete(datat, r) +} + +// Purge removes request data stored for longer than maxAge, in seconds. +// It returns the amount of requests removed. +// +// If maxAge <= 0, all request data is removed. +// +// This is only used for sanity check: in case context cleaning was not +// properly set some request data can be kept forever, consuming an increasing +// amount of memory. In case this is detected, Purge() must be called +// periodically until the problem is fixed. +func Purge(maxAge int) int { + mutex.Lock() + count := 0 + if maxAge <= 0 { + count = len(data) + data = make(map[*http.Request]map[interface{}]interface{}) + datat = make(map[*http.Request]int64) + } else { + min := time.Now().Unix() - int64(maxAge) + for r := range data { + if datat[r] < min { + clear(r) + count++ + } + } + } + mutex.Unlock() + return count +} + +// ClearHandler wraps an http.Handler and clears request values at the end +// of a request lifetime. +func ClearHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer Clear(r) + h.ServeHTTP(w, r) + }) +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/context_test.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/context_test.go new file mode 100644 index 0000000000..9814c501e8 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/context_test.go @@ -0,0 +1,161 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "net/http" + "testing" +) + +type keyType int + +const ( + key1 keyType = iota + key2 +) + +func TestContext(t *testing.T) { + assertEqual := func(val interface{}, exp interface{}) { + if val != exp { + t.Errorf("Expected %v, got %v.", exp, val) + } + } + + r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + + // Get() + assertEqual(Get(r, key1), nil) + + // Set() + Set(r, key1, "1") + assertEqual(Get(r, key1), "1") + assertEqual(len(data[r]), 1) + + Set(r, key2, "2") + assertEqual(Get(r, key2), "2") + assertEqual(len(data[r]), 2) + + //GetOk + value, ok := GetOk(r, key1) + assertEqual(value, "1") + assertEqual(ok, true) + + value, ok = GetOk(r, "not exists") + assertEqual(value, nil) + assertEqual(ok, false) + + Set(r, "nil value", nil) + value, ok = GetOk(r, "nil value") + assertEqual(value, nil) + assertEqual(ok, true) + + // GetAll() + values := GetAll(r) + assertEqual(len(values), 3) + + // GetAll() for empty request + values = GetAll(emptyR) + if values != nil { + t.Error("GetAll didn't return nil value for invalid request") + } + + // GetAllOk() + values, ok = GetAllOk(r) + assertEqual(len(values), 3) + assertEqual(ok, true) + + // GetAllOk() for empty request + values, ok = GetAllOk(emptyR) + assertEqual(value, nil) + assertEqual(ok, false) + + // Delete() + Delete(r, key1) + assertEqual(Get(r, key1), nil) + assertEqual(len(data[r]), 2) + + Delete(r, key2) + assertEqual(Get(r, key2), nil) + assertEqual(len(data[r]), 1) + + // Clear() + Clear(r) + assertEqual(len(data), 0) +} + +func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Set(r, key, value) + } + done <- struct{}{} + +} + +func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { + + b.StopTimer() + r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + done := make(chan struct{}) + b.StartTimer() + + for i := 0; i < b.N; i++ { + wait := make(chan struct{}) + + for i := 0; i < numReaders; i++ { + go parallelReader(r, "test", iterations, wait, done) + } + + for i := 0; i < numWriters; i++ { + go parallelWriter(r, "test", "123", iterations, wait, done) + } + + close(wait) + + for i := 0; i < numReaders+numWriters; i++ { + <-done + } + + } + +} + +func BenchmarkMutexSameReadWrite1(b *testing.B) { + benchmarkMutex(b, 1, 1, 32) +} +func BenchmarkMutexSameReadWrite2(b *testing.B) { + benchmarkMutex(b, 2, 2, 32) +} +func BenchmarkMutexSameReadWrite4(b *testing.B) { + benchmarkMutex(b, 4, 4, 32) +} +func BenchmarkMutex1(b *testing.B) { + benchmarkMutex(b, 2, 8, 32) +} +func BenchmarkMutex2(b *testing.B) { + benchmarkMutex(b, 16, 4, 64) +} +func BenchmarkMutex3(b *testing.B) { + benchmarkMutex(b, 1, 2, 128) +} +func BenchmarkMutex4(b *testing.B) { + benchmarkMutex(b, 128, 32, 256) +} +func BenchmarkMutex5(b *testing.B) { + benchmarkMutex(b, 1024, 2048, 64) +} +func BenchmarkMutex6(b *testing.B) { + benchmarkMutex(b, 2048, 1024, 512) +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/doc.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/doc.go new file mode 100644 index 0000000000..73c7400311 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/context/doc.go @@ -0,0 +1,82 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package context stores values shared during a request lifetime. + +For example, a router can set variables extracted from the URL and later +application handlers can access those values, or it can be used to store +sessions values to be saved at the end of a request. There are several +others common uses. + +The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: + + http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 + +Here's the basic usage: first define the keys that you will need. The key +type is interface{} so a key can be of any type that supports equality. +Here we define a key using a custom int type to avoid name collisions: + + package foo + + import ( + "github.com/gorilla/context" + ) + + type key int + + const MyKey key = 0 + +Then set a variable. Variables are bound to an http.Request object, so you +need a request instance to set a value: + + context.Set(r, MyKey, "bar") + +The application can later access the variable using the same key you provided: + + func MyHandler(w http.ResponseWriter, r *http.Request) { + // val is "bar". + val := context.Get(r, foo.MyKey) + + // returns ("bar", true) + val, ok := context.GetOk(r, foo.MyKey) + // ... + } + +And that's all about the basic usage. We discuss some other ideas below. + +Any type can be stored in the context. To enforce a given type, make the key +private and wrap Get() and Set() to accept and return values of a specific +type: + + type key int + + const mykey key = 0 + + // GetMyKey returns a value for this package from the request values. + func GetMyKey(r *http.Request) SomeType { + if rv := context.Get(r, mykey); rv != nil { + return rv.(SomeType) + } + return nil + } + + // SetMyKey sets a value for this package in the request values. + func SetMyKey(r *http.Request, val SomeType) { + context.Set(r, mykey, val) + } + +Variables must be cleared at the end of a request, to remove all values +that were stored. This can be done in an http.Handler, after a request was +served. Just call Clear() passing the request: + + context.Clear(r) + +...or use ClearHandler(), which conveniently wraps an http.Handler to clear +variables at the end of a request lifetime. + +The Routers from the packages gorilla/mux and gorilla/pat call Clear() +so if you are using either of them you don't need to clear the context manually. +*/ +package context diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml new file mode 100644 index 0000000000..d87d465768 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - tip diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE new file mode 100644 index 0000000000..0e5fb87280 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Rodrigo Moraes. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/README.md b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/README.md new file mode 100644 index 0000000000..e60301b033 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/README.md @@ -0,0 +1,7 @@ +mux +=== +[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) + +gorilla/mux is a powerful URL router and dispatcher. + +Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go new file mode 100644 index 0000000000..c5f97b2b2a --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go @@ -0,0 +1,21 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "net/http" + "testing" +) + +func BenchmarkMux(b *testing.B) { + router := new(Router) + handler := func(w http.ResponseWriter, r *http.Request) {} + router.HandleFunc("/v1/{v1}", handler) + + request, _ := http.NewRequest("GET", "/v1/anything", nil) + for i := 0; i < b.N; i++ { + router.ServeHTTP(nil, request) + } +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/doc.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/doc.go new file mode 100644 index 0000000000..b2deed34c4 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/doc.go @@ -0,0 +1,199 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package gorilla/mux implements a request router and dispatcher. + +The name mux stands for "HTTP request multiplexer". Like the standard +http.ServeMux, mux.Router matches incoming requests against a list of +registered routes and calls a handler for the route that matches the URL +or other conditions. The main features are: + + * Requests can be matched based on URL host, path, path prefix, schemes, + header and query values, HTTP methods or using custom matchers. + * URL hosts and paths can have variables with an optional regular + expression. + * Registered URLs can be built, or "reversed", which helps maintaining + references to resources. + * Routes can be used as subrouters: nested routes are only tested if the + parent route matches. This is useful to define groups of routes that + share common conditions like a host, a path prefix or other repeated + attributes. As a bonus, this optimizes request matching. + * It implements the http.Handler interface so it is compatible with the + standard http.ServeMux. + +Let's start registering a couple of URL paths and handlers: + + func main() { + r := mux.NewRouter() + r.HandleFunc("/", HomeHandler) + r.HandleFunc("/products", ProductsHandler) + r.HandleFunc("/articles", ArticlesHandler) + http.Handle("/", r) + } + +Here we register three routes mapping URL paths to handlers. This is +equivalent to how http.HandleFunc() works: if an incoming request URL matches +one of the paths, the corresponding handler is called passing +(http.ResponseWriter, *http.Request) as parameters. + +Paths can have variables. They are defined using the format {name} or +{name:pattern}. If a regular expression pattern is not defined, the matched +variable will be anything until the next slash. For example: + + r := mux.NewRouter() + r.HandleFunc("/products/{key}", ProductHandler) + r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) + r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) + +The names are used to create a map of route variables which can be retrieved +calling mux.Vars(): + + vars := mux.Vars(request) + category := vars["category"] + +And this is all you need to know about the basic usage. More advanced options +are explained below. + +Routes can also be restricted to a domain or subdomain. Just define a host +pattern to be matched. They can also have variables: + + r := mux.NewRouter() + // Only matches if domain is "www.domain.com". + r.Host("www.domain.com") + // Matches a dynamic subdomain. + r.Host("{subdomain:[a-z]+}.domain.com") + +There are several other matchers that can be added. To match path prefixes: + + r.PathPrefix("/products/") + +...or HTTP methods: + + r.Methods("GET", "POST") + +...or URL schemes: + + r.Schemes("https") + +...or header values: + + r.Headers("X-Requested-With", "XMLHttpRequest") + +...or query values: + + r.Queries("key", "value") + +...or to use a custom matcher function: + + r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { + return r.ProtoMajor == 0 + }) + +...and finally, it is possible to combine several matchers in a single route: + + r.HandleFunc("/products", ProductsHandler). + Host("www.domain.com"). + Methods("GET"). + Schemes("http") + +Setting the same matching conditions again and again can be boring, so we have +a way to group several routes that share the same requirements. +We call it "subrouting". + +For example, let's say we have several URLs that should only match when the +host is "www.domain.com". Create a route for that host and get a "subrouter" +from it: + + r := mux.NewRouter() + s := r.Host("www.domain.com").Subrouter() + +Then register routes in the subrouter: + + s.HandleFunc("/products/", ProductsHandler) + s.HandleFunc("/products/{key}", ProductHandler) + s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) + +The three URL paths we registered above will only be tested if the domain is +"www.domain.com", because the subrouter is tested first. This is not +only convenient, but also optimizes request matching. You can create +subrouters combining any attribute matchers accepted by a route. + +Subrouters can be used to create domain or path "namespaces": you define +subrouters in a central place and then parts of the app can register its +paths relatively to a given subrouter. + +There's one more thing about subroutes. When a subrouter has a path prefix, +the inner routes use it as base for their paths: + + r := mux.NewRouter() + s := r.PathPrefix("/products").Subrouter() + // "/products/" + s.HandleFunc("/", ProductsHandler) + // "/products/{key}/" + s.HandleFunc("/{key}/", ProductHandler) + // "/products/{key}/details" + s.HandleFunc("/{key}/details", ProductDetailsHandler) + +Now let's see how to build registered URLs. + +Routes can be named. All routes that define a name can have their URLs built, +or "reversed". We define a name calling Name() on a route. For example: + + r := mux.NewRouter() + r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). + Name("article") + +To build a URL, get the route and call the URL() method, passing a sequence of +key/value pairs for the route variables. For the previous route, we would do: + + url, err := r.Get("article").URL("category", "technology", "id", "42") + +...and the result will be a url.URL with the following path: + + "/articles/technology/42" + +This also works for host variables: + + r := mux.NewRouter() + r.Host("{subdomain}.domain.com"). + Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + + // url.String() will be "http://news.domain.com/articles/technology/42" + url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") + +All variables defined in the route are required, and their values must +conform to the corresponding patterns. These requirements guarantee that a +generated URL will always match a registered route -- the only exception is +for explicitly defined "build-only" routes which never match. + +There's also a way to build only the URL host or path for a route: +use the methods URLHost() or URLPath() instead. For the previous route, +we would do: + + // "http://news.domain.com/" + host, err := r.Get("article").URLHost("subdomain", "news") + + // "/articles/technology/42" + path, err := r.Get("article").URLPath("category", "technology", "id", "42") + +And if you use subrouters, host and path defined separately can be built +as well: + + r := mux.NewRouter() + s := r.Host("{subdomain}.domain.com").Subrouter() + s.Path("/articles/{category}/{id:[0-9]+}"). + HandlerFunc(ArticleHandler). + Name("article") + + // "http://news.domain.com/articles/technology/42" + url, err := r.Get("article").URL("subdomain", "news", + "category", "technology", + "id", "42") +*/ +package mux diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/mux.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/mux.go new file mode 100644 index 0000000000..af31d23955 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/mux.go @@ -0,0 +1,366 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "fmt" + "net/http" + "path" + + "github.com/gorilla/context" +) + +// NewRouter returns a new router instance. +func NewRouter() *Router { + return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} +} + +// Router registers routes to be matched and dispatches a handler. +// +// It implements the http.Handler interface, so it can be registered to serve +// requests: +// +// var router = mux.NewRouter() +// +// func main() { +// http.Handle("/", router) +// } +// +// Or, for Google App Engine, register it in a init() function: +// +// func init() { +// http.Handle("/", router) +// } +// +// This will send all incoming requests to the router. +type Router struct { + // Configurable Handler to be used when no route matches. + NotFoundHandler http.Handler + // Parent route, if this is a subrouter. + parent parentRoute + // Routes to be matched, in order. + routes []*Route + // Routes by name for URL building. + namedRoutes map[string]*Route + // See Router.StrictSlash(). This defines the flag for new routes. + strictSlash bool + // If true, do not clear the request context after handling the request + KeepContext bool +} + +// Match matches registered routes against the request. +func (r *Router) Match(req *http.Request, match *RouteMatch) bool { + for _, route := range r.routes { + if route.Match(req, match) { + return true + } + } + return false +} + +// ServeHTTP dispatches the handler registered in the matched route. +// +// When there is a match, the route variables can be retrieved calling +// mux.Vars(request). +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + // Clean path to canonical form and redirect. + if p := cleanPath(req.URL.Path); p != req.URL.Path { + + // Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. + // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: + // http://code.google.com/p/go/issues/detail?id=5252 + url := *req.URL + url.Path = p + p = url.String() + + w.Header().Set("Location", p) + w.WriteHeader(http.StatusMovedPermanently) + return + } + var match RouteMatch + var handler http.Handler + if r.Match(req, &match) { + handler = match.Handler + setVars(req, match.Vars) + setCurrentRoute(req, match.Route) + } + if handler == nil { + handler = r.NotFoundHandler + if handler == nil { + handler = http.NotFoundHandler() + } + } + if !r.KeepContext { + defer context.Clear(req) + } + handler.ServeHTTP(w, req) +} + +// Get returns a route registered with the given name. +func (r *Router) Get(name string) *Route { + return r.getNamedRoutes()[name] +} + +// GetRoute returns a route registered with the given name. This method +// was renamed to Get() and remains here for backwards compatibility. +func (r *Router) GetRoute(name string) *Route { + return r.getNamedRoutes()[name] +} + +// StrictSlash defines the trailing slash behavior for new routes. The initial +// value is false. +// +// When true, if the route path is "/path/", accessing "/path" will redirect +// to the former and vice versa. In other words, your application will always +// see the path as specified in the route. +// +// When false, if the route path is "/path", accessing "/path/" will not match +// this route and vice versa. +// +// Special case: when a route sets a path prefix using the PathPrefix() method, +// strict slash is ignored for that route because the redirect behavior can't +// be determined from a prefix alone. However, any subrouters created from that +// route inherit the original StrictSlash setting. +func (r *Router) StrictSlash(value bool) *Router { + r.strictSlash = value + return r +} + +// ---------------------------------------------------------------------------- +// parentRoute +// ---------------------------------------------------------------------------- + +// getNamedRoutes returns the map where named routes are registered. +func (r *Router) getNamedRoutes() map[string]*Route { + if r.namedRoutes == nil { + if r.parent != nil { + r.namedRoutes = r.parent.getNamedRoutes() + } else { + r.namedRoutes = make(map[string]*Route) + } + } + return r.namedRoutes +} + +// getRegexpGroup returns regexp definitions from the parent route, if any. +func (r *Router) getRegexpGroup() *routeRegexpGroup { + if r.parent != nil { + return r.parent.getRegexpGroup() + } + return nil +} + +func (r *Router) buildVars(m map[string]string) map[string]string { + if r.parent != nil { + m = r.parent.buildVars(m) + } + return m +} + +// ---------------------------------------------------------------------------- +// Route factories +// ---------------------------------------------------------------------------- + +// NewRoute registers an empty route. +func (r *Router) NewRoute() *Route { + route := &Route{parent: r, strictSlash: r.strictSlash} + r.routes = append(r.routes, route) + return route +} + +// Handle registers a new route with a matcher for the URL path. +// See Route.Path() and Route.Handler(). +func (r *Router) Handle(path string, handler http.Handler) *Route { + return r.NewRoute().Path(path).Handler(handler) +} + +// HandleFunc registers a new route with a matcher for the URL path. +// See Route.Path() and Route.HandlerFunc(). +func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, + *http.Request)) *Route { + return r.NewRoute().Path(path).HandlerFunc(f) +} + +// Headers registers a new route with a matcher for request header values. +// See Route.Headers(). +func (r *Router) Headers(pairs ...string) *Route { + return r.NewRoute().Headers(pairs...) +} + +// Host registers a new route with a matcher for the URL host. +// See Route.Host(). +func (r *Router) Host(tpl string) *Route { + return r.NewRoute().Host(tpl) +} + +// MatcherFunc registers a new route with a custom matcher function. +// See Route.MatcherFunc(). +func (r *Router) MatcherFunc(f MatcherFunc) *Route { + return r.NewRoute().MatcherFunc(f) +} + +// Methods registers a new route with a matcher for HTTP methods. +// See Route.Methods(). +func (r *Router) Methods(methods ...string) *Route { + return r.NewRoute().Methods(methods...) +} + +// Path registers a new route with a matcher for the URL path. +// See Route.Path(). +func (r *Router) Path(tpl string) *Route { + return r.NewRoute().Path(tpl) +} + +// PathPrefix registers a new route with a matcher for the URL path prefix. +// See Route.PathPrefix(). +func (r *Router) PathPrefix(tpl string) *Route { + return r.NewRoute().PathPrefix(tpl) +} + +// Queries registers a new route with a matcher for URL query values. +// See Route.Queries(). +func (r *Router) Queries(pairs ...string) *Route { + return r.NewRoute().Queries(pairs...) +} + +// Schemes registers a new route with a matcher for URL schemes. +// See Route.Schemes(). +func (r *Router) Schemes(schemes ...string) *Route { + return r.NewRoute().Schemes(schemes...) +} + +// BuildVars registers a new route with a custom function for modifying +// route variables before building a URL. +func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { + return r.NewRoute().BuildVarsFunc(f) +} + +// ---------------------------------------------------------------------------- +// Context +// ---------------------------------------------------------------------------- + +// RouteMatch stores information about a matched route. +type RouteMatch struct { + Route *Route + Handler http.Handler + Vars map[string]string +} + +type contextKey int + +const ( + varsKey contextKey = iota + routeKey +) + +// Vars returns the route variables for the current request, if any. +func Vars(r *http.Request) map[string]string { + if rv := context.Get(r, varsKey); rv != nil { + return rv.(map[string]string) + } + return nil +} + +// CurrentRoute returns the matched route for the current request, if any. +func CurrentRoute(r *http.Request) *Route { + if rv := context.Get(r, routeKey); rv != nil { + return rv.(*Route) + } + return nil +} + +func setVars(r *http.Request, val interface{}) { + context.Set(r, varsKey, val) +} + +func setCurrentRoute(r *http.Request, val interface{}) { + context.Set(r, routeKey, val) +} + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +// cleanPath returns the canonical path for p, eliminating . and .. elements. +// Borrowed from the net/http package. +func cleanPath(p string) string { + if p == "" { + return "/" + } + if p[0] != '/' { + p = "/" + p + } + np := path.Clean(p) + // path.Clean removes trailing slash except for root; + // put the trailing slash back if necessary. + if p[len(p)-1] == '/' && np != "/" { + np += "/" + } + return np +} + +// uniqueVars returns an error if two slices contain duplicated strings. +func uniqueVars(s1, s2 []string) error { + for _, v1 := range s1 { + for _, v2 := range s2 { + if v1 == v2 { + return fmt.Errorf("mux: duplicated route variable %q", v2) + } + } + } + return nil +} + +// mapFromPairs converts variadic string parameters to a string map. +func mapFromPairs(pairs ...string) (map[string]string, error) { + length := len(pairs) + if length%2 != 0 { + return nil, fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + } + m := make(map[string]string, length/2) + for i := 0; i < length; i += 2 { + m[pairs[i]] = pairs[i+1] + } + return m, nil +} + +// matchInArray returns true if the given string value is in the array. +func matchInArray(arr []string, value string) bool { + for _, v := range arr { + if v == value { + return true + } + } + return false +} + +// matchMap returns true if the given key/value pairs exist in a given map. +func matchMap(toCheck map[string]string, toMatch map[string][]string, + canonicalKey bool) bool { + for k, v := range toCheck { + // Check if key exists. + if canonicalKey { + k = http.CanonicalHeaderKey(k) + } + if values := toMatch[k]; values == nil { + return false + } else if v != "" { + // If value was defined as an empty string we only check that the + // key exists. Otherwise we also check for equality. + valueExists := false + for _, value := range values { + if v == value { + valueExists = true + break + } + } + if !valueExists { + return false + } + } + } + return true +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go new file mode 100644 index 0000000000..075dedba48 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go @@ -0,0 +1,1003 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gorilla/context" +) + +type routeTest struct { + title string // title of the test + route *Route // the route being tested + request *http.Request // a request to test the route + vars map[string]string // the expected vars of the match + host string // the expected host of the match + path string // the expected path of the match + shouldMatch bool // whether the request is expected to match the route at all + shouldRedirect bool // whether the request should result in a redirect +} + +func TestHost(t *testing.T) { + // newRequestHost a new request with a method, url, and host header + newRequestHost := func(method, url, host string) *http.Request { + req, err := http.NewRequest(method, url, nil) + if err != nil { + panic(err) + } + req.Host = host + return req + } + + tests := []routeTest{ + { + title: "Host route match", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route, wrong host in request URL", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + { + title: "Host route with port, match", + route: new(Route).Host("aaa.bbb.ccc:1234"), + request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc:1234", + path: "", + shouldMatch: true, + }, + { + title: "Host route with port, wrong port in request URL", + route: new(Route).Host("aaa.bbb.ccc:1234"), + request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"), + vars: map[string]string{}, + host: "aaa.bbb.ccc:1234", + path: "", + shouldMatch: false, + }, + { + title: "Host route, match with host in request header", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route, wrong host in request header", + route: new(Route).Host("aaa.bbb.ccc"), + request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"), + vars: map[string]string{}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + // BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true}, + { + title: "Host route with port, wrong host in request header", + route: new(Route).Host("aaa.bbb.ccc:1234"), + request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"), + vars: map[string]string{}, + host: "aaa.bbb.ccc:1234", + path: "", + shouldMatch: false, + }, + { + title: "Host route with pattern, match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route with pattern, wrong host in request URL", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + { + title: "Host route with multiple patterns, match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: true, + }, + { + title: "Host route with multiple patterns, wrong host in request URL", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"}, + host: "aaa.bbb.ccc", + path: "", + shouldMatch: false, + }, + { + title: "Path route with single pattern with pipe, match", + route: new(Route).Path("/{category:a|b/c}"), + request: newRequest("GET", "http://localhost/a"), + vars: map[string]string{"category": "a"}, + host: "", + path: "/a", + shouldMatch: true, + }, + { + title: "Path route with single pattern with pipe, match", + route: new(Route).Path("/{category:a|b/c}"), + request: newRequest("GET", "http://localhost/b/c"), + vars: map[string]string{"category": "b/c"}, + host: "", + path: "/b/c", + shouldMatch: true, + }, + { + title: "Path route with multiple patterns with pipe, match", + route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"), + request: newRequest("GET", "http://localhost/a/product_name/1"), + vars: map[string]string{"category": "a", "product": "product_name", "id": "1"}, + host: "", + path: "/a/product_name/1", + shouldMatch: true, + }, + } + for _, test := range tests { + testRoute(t, test) + } +} + +func TestPath(t *testing.T) { + tests := []routeTest{ + { + title: "Path route, match", + route: new(Route).Path("/111/222/333"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Path route, match with trailing slash in request and path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + }, + { + title: "Path route, do not match with trailing slash in path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: false, + }, + { + title: "Path route, do not match with trailing slash in request", + route: new(Route).Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: false, + }, + { + title: "Path route, wrong path in request in request URL", + route: new(Route).Path("/111/222/333"), + request: newRequest("GET", "http://localhost/1/2/3"), + vars: map[string]string{}, + host: "", + path: "/111/222/333", + shouldMatch: false, + }, + { + title: "Path route with pattern, match", + route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Path route with pattern, URL in request does not match", + route: new(Route).Path("/111/{v1:[0-9]{3}}/333"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222/333", + shouldMatch: false, + }, + { + title: "Path route with multiple patterns, match", + route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, + host: "", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Path route with multiple patterns, URL in request does not match", + route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"}, + host: "", + path: "/111/222/333", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestPathPrefix(t *testing.T) { + tests := []routeTest{ + { + title: "PathPrefix route, match", + route: new(Route).PathPrefix("/111"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + }, + { + title: "PathPrefix route, match substring", + route: new(Route).PathPrefix("/1"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/1", + shouldMatch: true, + }, + { + title: "PathPrefix route, URL prefix in request does not match", + route: new(Route).PathPrefix("/111"), + request: newRequest("GET", "http://localhost/1/2/3"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: false, + }, + { + title: "PathPrefix route with pattern, match", + route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222", + shouldMatch: true, + }, + { + title: "PathPrefix route with pattern, URL prefix in request does not match", + route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "222"}, + host: "", + path: "/111/222", + shouldMatch: false, + }, + { + title: "PathPrefix route with multiple patterns, match", + route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{"v1": "111", "v2": "222"}, + host: "", + path: "/111/222", + shouldMatch: true, + }, + { + title: "PathPrefix route with multiple patterns, URL prefix in request does not match", + route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"), + request: newRequest("GET", "http://localhost/111/aaa/333"), + vars: map[string]string{"v1": "111", "v2": "222"}, + host: "", + path: "/111/222", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestHostPath(t *testing.T) { + tests := []routeTest{ + { + title: "Host and Path route, match", + route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Host and Path route, wrong host in request URL", + route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + { + title: "Host and Path route with pattern, match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Host and Path route with pattern, URL in request does not match", + route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "bbb", "v2": "222"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: false, + }, + { + title: "Host and Path route with multiple patterns, match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), + request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: true, + }, + { + title: "Host and Path route with multiple patterns, URL in request does not match", + route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"), + request: newRequest("GET", "http://aaa.222.ccc/111/222/333"), + vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"}, + host: "aaa.bbb.ccc", + path: "/111/222/333", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestHeaders(t *testing.T) { + // newRequestHeaders creates a new request with a method, url, and headers + newRequestHeaders := func(method, url string, headers map[string]string) *http.Request { + req, err := http.NewRequest(method, url, nil) + if err != nil { + panic(err) + } + for k, v := range headers { + req.Header.Add(k, v) + } + return req + } + + tests := []routeTest{ + { + title: "Headers route, match", + route: new(Route).Headers("foo", "bar", "baz", "ding"), + request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Headers route, bad header values", + route: new(Route).Headers("foo", "bar", "baz", "ding"), + request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } + +} + +func TestMethods(t *testing.T) { + tests := []routeTest{ + { + title: "Methods route, match GET", + route: new(Route).Methods("GET", "POST"), + request: newRequest("GET", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Methods route, match POST", + route: new(Route).Methods("GET", "POST"), + request: newRequest("POST", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Methods route, bad method", + route: new(Route).Methods("GET", "POST"), + request: newRequest("PUT", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestQueries(t *testing.T) { + tests := []routeTest{ + { + title: "Queries route, match", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route, match with a query string", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route, match with a query string out of order", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route, bad query", + route: new(Route).Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://localhost?foo=bar&baz=dong"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + { + title: "Queries route with pattern, match", + route: new(Route).Queries("foo", "{v1}"), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{"v1": "bar"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with multiple patterns, match", + route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=10"), + vars: map[string]string{"v1": "10"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=a"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestSchemes(t *testing.T) { + tests := []routeTest{ + // Schemes + { + title: "Schemes route, match https", + route: new(Route).Schemes("https", "ftp"), + request: newRequest("GET", "https://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Schemes route, match ftp", + route: new(Route).Schemes("https", "ftp"), + request: newRequest("GET", "ftp://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Schemes route, bad scheme", + route: new(Route).Schemes("https", "ftp"), + request: newRequest("GET", "http://localhost"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + for _, test := range tests { + testRoute(t, test) + } +} + +func TestMatcherFunc(t *testing.T) { + m := func(r *http.Request, m *RouteMatch) bool { + if r.URL.Host == "aaa.bbb.ccc" { + return true + } + return false + } + + tests := []routeTest{ + { + title: "MatchFunc route, match", + route: new(Route).MatcherFunc(m), + request: newRequest("GET", "http://aaa.bbb.ccc"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "MatchFunc route, non-match", + route: new(Route).MatcherFunc(m), + request: newRequest("GET", "http://aaa.222.ccc"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestBuildVarsFunc(t *testing.T) { + tests := []routeTest{ + { + title: "BuildVarsFunc set on route", + route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string { + vars["v1"] = "3" + vars["v2"] = "a" + return vars + }), + request: newRequest("GET", "http://localhost/111/2"), + path: "/111/3a", + shouldMatch: true, + }, + { + title: "BuildVarsFunc set on route and parent route", + route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string { + vars["v1"] = "2" + return vars + }).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string { + vars["v2"] = "b" + return vars + }), + request: newRequest("GET", "http://localhost/1/a"), + path: "/2/b", + shouldMatch: true, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestSubRouter(t *testing.T) { + subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() + subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter() + + tests := []routeTest{ + { + route: subrouter1.Path("/{v2:[a-z]+}"), + request: newRequest("GET", "http://aaa.google.com/bbb"), + vars: map[string]string{"v1": "aaa", "v2": "bbb"}, + host: "aaa.google.com", + path: "/bbb", + shouldMatch: true, + }, + { + route: subrouter1.Path("/{v2:[a-z]+}"), + request: newRequest("GET", "http://111.google.com/111"), + vars: map[string]string{"v1": "aaa", "v2": "bbb"}, + host: "aaa.google.com", + path: "/bbb", + shouldMatch: false, + }, + { + route: subrouter2.Path("/baz/{v2}"), + request: newRequest("GET", "http://localhost/foo/bar/baz/ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "/foo/bar/baz/ding", + shouldMatch: true, + }, + { + route: subrouter2.Path("/baz/{v2}"), + request: newRequest("GET", "http://localhost/foo/bar"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "/foo/bar/baz/ding", + shouldMatch: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +func TestNamedRoutes(t *testing.T) { + r1 := NewRouter() + r1.NewRoute().Name("a") + r1.NewRoute().Name("b") + r1.NewRoute().Name("c") + + r2 := r1.NewRoute().Subrouter() + r2.NewRoute().Name("d") + r2.NewRoute().Name("e") + r2.NewRoute().Name("f") + + r3 := r2.NewRoute().Subrouter() + r3.NewRoute().Name("g") + r3.NewRoute().Name("h") + r3.NewRoute().Name("i") + + if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 { + t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes) + } else if r1.Get("i") == nil { + t.Errorf("Subroute name not registered") + } +} + +func TestStrictSlash(t *testing.T) { + r := NewRouter() + r.StrictSlash(true) + + tests := []routeTest{ + { + title: "Redirect path without slash", + route: r.NewRoute().Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Do not redirect path with slash", + route: r.NewRoute().Path("/111/"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + shouldRedirect: false, + }, + { + title: "Redirect path with slash", + route: r.NewRoute().Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Do not redirect path without slash", + route: r.NewRoute().Path("/111"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + shouldRedirect: false, + }, + { + title: "Propagate StrictSlash to subrouters", + route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"), + request: newRequest("GET", "http://localhost/static/images"), + vars: map[string]string{}, + host: "", + path: "/static/images/", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Ignore StrictSlash for path prefix", + route: r.NewRoute().PathPrefix("/static/"), + request: newRequest("GET", "http://localhost/static/logo.png"), + vars: map[string]string{}, + host: "", + path: "/static/", + shouldMatch: true, + shouldRedirect: false, + }, + } + + for _, test := range tests { + testRoute(t, test) + } +} + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +func getRouteTemplate(route *Route) string { + host, path := "none", "none" + if route.regexp != nil { + if route.regexp.host != nil { + host = route.regexp.host.template + } + if route.regexp.path != nil { + path = route.regexp.path.template + } + } + return fmt.Sprintf("Host: %v, Path: %v", host, path) +} + +func testRoute(t *testing.T, test routeTest) { + request := test.request + route := test.route + vars := test.vars + shouldMatch := test.shouldMatch + host := test.host + path := test.path + url := test.host + test.path + shouldRedirect := test.shouldRedirect + + var match RouteMatch + ok := route.Match(request, &match) + if ok != shouldMatch { + msg := "Should match" + if !shouldMatch { + msg = "Should not match" + } + t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars) + return + } + if shouldMatch { + if test.vars != nil && !stringMapEqual(test.vars, match.Vars) { + t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars) + return + } + if host != "" { + u, _ := test.route.URLHost(mapToPairs(match.Vars)...) + if host != u.Host { + t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route)) + return + } + } + if path != "" { + u, _ := route.URLPath(mapToPairs(match.Vars)...) + if path != u.Path { + t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route)) + return + } + } + if url != "" { + u, _ := route.URL(mapToPairs(match.Vars)...) + if url != u.Host+u.Path { + t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route)) + return + } + } + if shouldRedirect && match.Handler == nil { + t.Errorf("(%v) Did not redirect", test.title) + return + } + if !shouldRedirect && match.Handler != nil { + t.Errorf("(%v) Unexpected redirect", test.title) + return + } + } +} + +// Tests that the context is cleared or not cleared properly depending on +// the configuration of the router +func TestKeepContext(t *testing.T) { + func1 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + res := new(http.ResponseWriter) + r.ServeHTTP(*res, req) + + if _, ok := context.GetOk(req, "t"); ok { + t.Error("Context should have been cleared at end of request") + } + + r.KeepContext = true + + req, _ = http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + r.ServeHTTP(*res, req) + if _, ok := context.GetOk(req, "t"); !ok { + t.Error("Context should NOT have been cleared at end of request") + } + +} + +type TestA301ResponseWriter struct { + hh http.Header + status int +} + +func (ho TestA301ResponseWriter) Header() http.Header { + return http.Header(ho.hh) +} + +func (ho TestA301ResponseWriter) Write(b []byte) (int, error) { + return 0, nil +} + +func (ho TestA301ResponseWriter) WriteHeader(code int) { + ho.status = code +} + +func Test301Redirect(t *testing.T) { + m := make(http.Header) + + func1 := func(w http.ResponseWriter, r *http.Request) {} + func2 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/api/", func2).Name("func2") + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil) + + res := TestA301ResponseWriter{ + hh: m, + status: 0, + } + r.ServeHTTP(&res, req) + + if "http://localhost/api/?abc=def" != res.hh["Location"][0] { + t.Errorf("Should have complete URL with query string") + } +} + +// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW +func TestSubrouterHeader(t *testing.T) { + expected := "func1 response" + func1 := func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, expected) + } + func2 := func(http.ResponseWriter, *http.Request) {} + + r := NewRouter() + s := r.Headers("SomeSpecialHeader", "").Subrouter() + s.HandleFunc("/", func1).Name("func1") + r.HandleFunc("/", func2).Name("func2") + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + req.Header.Add("SomeSpecialHeader", "foo") + match := new(RouteMatch) + matched := r.Match(req, match) + if !matched { + t.Errorf("Should match request") + } + if match.Route.GetName() != "func1" { + t.Errorf("Expecting func1 handler, got %s", match.Route.GetName()) + } + resp := NewRecorder() + match.Handler.ServeHTTP(resp, req) + if resp.Body.String() != expected { + t.Errorf("Expecting %q", expected) + } +} + +// mapToPairs converts a string map to a slice of string pairs +func mapToPairs(m map[string]string) []string { + var i int + p := make([]string, len(m)*2) + for k, v := range m { + p[i] = k + p[i+1] = v + i += 2 + } + return p +} + +// stringMapEqual checks the equality of two string maps +func stringMapEqual(m1, m2 map[string]string) bool { + nil1 := m1 == nil + nil2 := m2 == nil + if nil1 != nil2 || len(m1) != len(m2) { + return false + } + for k, v := range m1 { + if v != m2[k] { + return false + } + } + return true +} + +// newRequest is a helper function to create a new request with a method and url +func newRequest(method, url string) *http.Request { + req, err := http.NewRequest(method, url, nil) + if err != nil { + panic(err) + } + return req +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go new file mode 100644 index 0000000000..1f7c190c0f --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go @@ -0,0 +1,714 @@ +// Old tests ported to Go1. This is a mess. Want to drop it one day. + +// Copyright 2011 Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "bytes" + "net/http" + "testing" +) + +// ---------------------------------------------------------------------------- +// ResponseRecorder +// ---------------------------------------------------------------------------- +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// ResponseRecorder is an implementation of http.ResponseWriter that +// records its mutations for later inspection in tests. +type ResponseRecorder struct { + Code int // the HTTP response code from WriteHeader + HeaderMap http.Header // the HTTP response headers + Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to + Flushed bool +} + +// NewRecorder returns an initialized ResponseRecorder. +func NewRecorder() *ResponseRecorder { + return &ResponseRecorder{ + HeaderMap: make(http.Header), + Body: new(bytes.Buffer), + } +} + +// DefaultRemoteAddr is the default remote address to return in RemoteAddr if +// an explicit DefaultRemoteAddr isn't set on ResponseRecorder. +const DefaultRemoteAddr = "1.2.3.4" + +// Header returns the response headers. +func (rw *ResponseRecorder) Header() http.Header { + return rw.HeaderMap +} + +// Write always succeeds and writes to rw.Body, if not nil. +func (rw *ResponseRecorder) Write(buf []byte) (int, error) { + if rw.Body != nil { + rw.Body.Write(buf) + } + if rw.Code == 0 { + rw.Code = http.StatusOK + } + return len(buf), nil +} + +// WriteHeader sets rw.Code. +func (rw *ResponseRecorder) WriteHeader(code int) { + rw.Code = code +} + +// Flush sets rw.Flushed to true. +func (rw *ResponseRecorder) Flush() { + rw.Flushed = true +} + +// ---------------------------------------------------------------------------- + +func TestRouteMatchers(t *testing.T) { + var scheme, host, path, query, method string + var headers map[string]string + var resultVars map[bool]map[string]string + + router := NewRouter() + router.NewRoute().Host("{var1}.google.com"). + Path("/{var2:[a-z]+}/{var3:[0-9]+}"). + Queries("foo", "bar"). + Methods("GET"). + Schemes("https"). + Headers("x-requested-with", "XMLHttpRequest") + router.NewRoute().Host("www.{var4}.com"). + PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}"). + Queries("baz", "ding"). + Methods("POST"). + Schemes("http"). + Headers("Content-Type", "application/json") + + reset := func() { + // Everything match. + scheme = "https" + host = "www.google.com" + path = "/product/42" + query = "?foo=bar" + method = "GET" + headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} + resultVars = map[bool]map[string]string{ + true: {"var1": "www", "var2": "product", "var3": "42"}, + false: {}, + } + } + + reset2 := func() { + // Everything match. + scheme = "http" + host = "www.google.com" + path = "/foo/product/42/path/that/is/ignored" + query = "?baz=ding" + method = "POST" + headers = map[string]string{"Content-Type": "application/json"} + resultVars = map[bool]map[string]string{ + true: {"var4": "google", "var5": "product", "var6": "42"}, + false: {}, + } + } + + match := func(shouldMatch bool) { + url := scheme + "://" + host + path + query + request, _ := http.NewRequest(method, url, nil) + for key, value := range headers { + request.Header.Add(key, value) + } + + var routeMatch RouteMatch + matched := router.Match(request, &routeMatch) + if matched != shouldMatch { + // Need better messages. :) + if matched { + t.Errorf("Should match.") + } else { + t.Errorf("Should not match.") + } + } + + if matched { + currentRoute := routeMatch.Route + if currentRoute == nil { + t.Errorf("Expected a current route.") + } + vars := routeMatch.Vars + expectedVars := resultVars[shouldMatch] + if len(vars) != len(expectedVars) { + t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) + } + for name, value := range vars { + if expectedVars[name] != value { + t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) + } + } + } + } + + // 1st route -------------------------------------------------------------- + + // Everything match. + reset() + match(true) + + // Scheme doesn't match. + reset() + scheme = "http" + match(false) + + // Host doesn't match. + reset() + host = "www.mygoogle.com" + match(false) + + // Path doesn't match. + reset() + path = "/product/notdigits" + match(false) + + // Query doesn't match. + reset() + query = "?foo=baz" + match(false) + + // Method doesn't match. + reset() + method = "POST" + match(false) + + // Header doesn't match. + reset() + headers = map[string]string{} + match(false) + + // Everything match, again. + reset() + match(true) + + // 2nd route -------------------------------------------------------------- + + // Everything match. + reset2() + match(true) + + // Scheme doesn't match. + reset2() + scheme = "https" + match(false) + + // Host doesn't match. + reset2() + host = "sub.google.com" + match(false) + + // Path doesn't match. + reset2() + path = "/bar/product/42" + match(false) + + // Query doesn't match. + reset2() + query = "?foo=baz" + match(false) + + // Method doesn't match. + reset2() + method = "GET" + match(false) + + // Header doesn't match. + reset2() + headers = map[string]string{} + match(false) + + // Everything match, again. + reset2() + match(true) +} + +type headerMatcherTest struct { + matcher headerMatcher + headers map[string]string + result bool +} + +var headerMatcherTests = []headerMatcherTest{ + { + matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), + headers: map[string]string{"X-Requested-With": "XMLHttpRequest"}, + result: true, + }, + { + matcher: headerMatcher(map[string]string{"x-requested-with": ""}), + headers: map[string]string{"X-Requested-With": "anything"}, + result: true, + }, + { + matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), + headers: map[string]string{}, + result: false, + }, +} + +type hostMatcherTest struct { + matcher *Route + url string + vars map[string]string + result bool +} + +var hostMatcherTests = []hostMatcherTest{ + { + matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), + url: "http://abc.def.ghi/", + vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, + result: true, + }, + { + matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), + url: "http://a.b.c/", + vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, + result: false, + }, +} + +type methodMatcherTest struct { + matcher methodMatcher + method string + result bool +} + +var methodMatcherTests = []methodMatcherTest{ + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "GET", + result: true, + }, + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "POST", + result: true, + }, + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "PUT", + result: true, + }, + { + matcher: methodMatcher([]string{"GET", "POST", "PUT"}), + method: "DELETE", + result: false, + }, +} + +type pathMatcherTest struct { + matcher *Route + url string + vars map[string]string + result bool +} + +var pathMatcherTests = []pathMatcherTest{ + { + matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), + url: "http://localhost:8080/123/456/789", + vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, + result: true, + }, + { + matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), + url: "http://localhost:8080/1/2/3", + vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"}, + result: false, + }, +} + +type schemeMatcherTest struct { + matcher schemeMatcher + url string + result bool +} + +var schemeMatcherTests = []schemeMatcherTest{ + { + matcher: schemeMatcher([]string{"http", "https"}), + url: "http://localhost:8080/", + result: true, + }, + { + matcher: schemeMatcher([]string{"http", "https"}), + url: "https://localhost:8080/", + result: true, + }, + { + matcher: schemeMatcher([]string{"https"}), + url: "http://localhost:8080/", + result: false, + }, + { + matcher: schemeMatcher([]string{"http"}), + url: "https://localhost:8080/", + result: false, + }, +} + +type urlBuildingTest struct { + route *Route + vars []string + url string +} + +var urlBuildingTests = []urlBuildingTest{ + { + route: new(Route).Host("foo.domain.com"), + vars: []string{}, + url: "http://foo.domain.com", + }, + { + route: new(Route).Host("{subdomain}.domain.com"), + vars: []string{"subdomain", "bar"}, + url: "http://bar.domain.com", + }, + { + route: new(Route).Host("foo.domain.com").Path("/articles"), + vars: []string{}, + url: "http://foo.domain.com/articles", + }, + { + route: new(Route).Path("/articles"), + vars: []string{}, + url: "/articles", + }, + { + route: new(Route).Path("/articles/{category}/{id:[0-9]+}"), + vars: []string{"category", "technology", "id", "42"}, + url: "/articles/technology/42", + }, + { + route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"), + vars: []string{"subdomain", "foo", "category", "technology", "id", "42"}, + url: "http://foo.domain.com/articles/technology/42", + }, +} + +func TestHeaderMatcher(t *testing.T) { + for _, v := range headerMatcherTests { + request, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + for key, value := range v.headers { + request.Header.Add(key, value) + } + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, request.Header) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, request.Header) + } + } + } +} + +func TestHostMatcher(t *testing.T) { + for _, v := range hostMatcherTests { + request, _ := http.NewRequest("GET", v.url, nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + vars := routeMatch.Vars + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.url) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.url) + } + } + if result { + if len(vars) != len(v.vars) { + t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) + } + for name, value := range vars { + if v.vars[name] != value { + t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) + } + } + } else { + if len(vars) != 0 { + t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) + } + } + } +} + +func TestMethodMatcher(t *testing.T) { + for _, v := range methodMatcherTests { + request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.method) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.method) + } + } + } +} + +func TestPathMatcher(t *testing.T) { + for _, v := range pathMatcherTests { + request, _ := http.NewRequest("GET", v.url, nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + vars := routeMatch.Vars + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.url) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.url) + } + } + if result { + if len(vars) != len(v.vars) { + t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) + } + for name, value := range vars { + if v.vars[name] != value { + t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) + } + } + } else { + if len(vars) != 0 { + t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) + } + } + } +} + +func TestSchemeMatcher(t *testing.T) { + for _, v := range schemeMatcherTests { + request, _ := http.NewRequest("GET", v.url, nil) + var routeMatch RouteMatch + result := v.matcher.Match(request, &routeMatch) + if result != v.result { + if v.result { + t.Errorf("%#v: should match %v.", v.matcher, v.url) + } else { + t.Errorf("%#v: should not match %v.", v.matcher, v.url) + } + } + } +} + +func TestUrlBuilding(t *testing.T) { + + for _, v := range urlBuildingTests { + u, _ := v.route.URL(v.vars...) + url := u.String() + if url != v.url { + t.Errorf("expected %v, got %v", v.url, url) + /* + reversePath := "" + reverseHost := "" + if v.route.pathTemplate != nil { + reversePath = v.route.pathTemplate.Reverse + } + if v.route.hostTemplate != nil { + reverseHost = v.route.hostTemplate.Reverse + } + + t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost) + */ + } + } + + ArticleHandler := func(w http.ResponseWriter, r *http.Request) { + } + + router := NewRouter() + router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article") + + url, _ := router.Get("article").URL("category", "technology", "id", "42") + expected := "/articles/technology/42" + if url.String() != expected { + t.Errorf("Expected %v, got %v", expected, url.String()) + } +} + +func TestMatchedRouteName(t *testing.T) { + routeName := "stock" + router := NewRouter() + route := router.NewRoute().Path("/products/").Name(routeName) + + url := "http://www.domain.com/products/" + request, _ := http.NewRequest("GET", url, nil) + var rv RouteMatch + ok := router.Match(request, &rv) + + if !ok || rv.Route != route { + t.Errorf("Expected same route, got %+v.", rv.Route) + } + + retName := rv.Route.GetName() + if retName != routeName { + t.Errorf("Expected %q, got %q.", routeName, retName) + } +} + +func TestSubRouting(t *testing.T) { + // Example from docs. + router := NewRouter() + subrouter := router.NewRoute().Host("www.domain.com").Subrouter() + route := subrouter.NewRoute().Path("/products/").Name("products") + + url := "http://www.domain.com/products/" + request, _ := http.NewRequest("GET", url, nil) + var rv RouteMatch + ok := router.Match(request, &rv) + + if !ok || rv.Route != route { + t.Errorf("Expected same route, got %+v.", rv.Route) + } + + u, _ := router.Get("products").URL() + builtUrl := u.String() + // Yay, subroute aware of the domain when building! + if builtUrl != url { + t.Errorf("Expected %q, got %q.", url, builtUrl) + } +} + +func TestVariableNames(t *testing.T) { + route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}") + if route.err == nil { + t.Errorf("Expected error for duplicated variable names") + } +} + +func TestRedirectSlash(t *testing.T) { + var route *Route + var routeMatch RouteMatch + r := NewRouter() + + r.StrictSlash(false) + route = r.NewRoute() + if route.strictSlash != false { + t.Errorf("Expected false redirectSlash.") + } + + r.StrictSlash(true) + route = r.NewRoute() + if route.strictSlash != true { + t.Errorf("Expected true redirectSlash.") + } + + route = new(Route) + route.strictSlash = true + route.Path("/{arg1}/{arg2:[0-9]+}/") + request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil) + routeMatch = RouteMatch{} + _ = route.Match(request, &routeMatch) + vars := routeMatch.Vars + if vars["arg1"] != "foo" { + t.Errorf("Expected foo.") + } + if vars["arg2"] != "123" { + t.Errorf("Expected 123.") + } + rsp := NewRecorder() + routeMatch.Handler.ServeHTTP(rsp, request) + if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" { + t.Errorf("Expected redirect header.") + } + + route = new(Route) + route.strictSlash = true + route.Path("/{arg1}/{arg2:[0-9]+}") + request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil) + routeMatch = RouteMatch{} + _ = route.Match(request, &routeMatch) + vars = routeMatch.Vars + if vars["arg1"] != "foo" { + t.Errorf("Expected foo.") + } + if vars["arg2"] != "123" { + t.Errorf("Expected 123.") + } + rsp = NewRecorder() + routeMatch.Handler.ServeHTTP(rsp, request) + if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" { + t.Errorf("Expected redirect header.") + } +} + +// Test for the new regexp library, still not available in stable Go. +func TestNewRegexp(t *testing.T) { + var p *routeRegexp + var matches []string + + tests := map[string]map[string][]string{ + "/{foo:a{2}}": { + "/a": nil, + "/aa": {"aa"}, + "/aaa": nil, + "/aaaa": nil, + }, + "/{foo:a{2,}}": { + "/a": nil, + "/aa": {"aa"}, + "/aaa": {"aaa"}, + "/aaaa": {"aaaa"}, + }, + "/{foo:a{2,3}}": { + "/a": nil, + "/aa": {"aa"}, + "/aaa": {"aaa"}, + "/aaaa": nil, + }, + "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": { + "/a": nil, + "/ab": nil, + "/abc": nil, + "/abcd": nil, + "/abc/ab": {"abc", "ab"}, + "/abc/abc": nil, + "/abcd/ab": nil, + }, + `/{foo:\w{3,}}/{bar:\d{2,}}`: { + "/a": nil, + "/ab": nil, + "/abc": nil, + "/abc/1": nil, + "/abc/12": {"abc", "12"}, + "/abcd/12": {"abcd", "12"}, + "/abcd/123": {"abcd", "123"}, + }, + } + + for pattern, paths := range tests { + p, _ = newRouteRegexp(pattern, false, false, false, false) + for path, result := range paths { + matches = p.regexp.FindStringSubmatch(path) + if result == nil { + if matches != nil { + t.Errorf("%v should not match %v.", pattern, path) + } + } else { + if len(matches) != len(result)+1 { + t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches)) + } else { + for k, v := range result { + if matches[k+1] != v { + t.Errorf("Expected %v, got %v.", v, matches[k+1]) + } + } + } + } + } + } +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go new file mode 100644 index 0000000000..aa3067986c --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go @@ -0,0 +1,272 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "bytes" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" +) + +// newRouteRegexp parses a route template and returns a routeRegexp, +// used to match a host, a path or a query string. +// +// It will extract named variables, assemble a regexp to be matched, create +// a "reverse" template to build URLs and compile regexps to validate variable +// values used in URL building. +// +// Previously we accepted only Python-like identifiers for variable +// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that +// name and pattern can't be empty, and names can't contain a colon. +func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { + // Check if it is well-formed. + idxs, errBraces := braceIndices(tpl) + if errBraces != nil { + return nil, errBraces + } + // Backup the original. + template := tpl + // Now let's parse it. + defaultPattern := "[^/]+" + if matchQuery { + defaultPattern = "[^?&]+" + matchPrefix = true + } else if matchHost { + defaultPattern = "[^.]+" + matchPrefix = false + } + // Only match strict slash if not matching + if matchPrefix || matchHost || matchQuery { + strictSlash = false + } + // Set a flag for strictSlash. + endSlash := false + if strictSlash && strings.HasSuffix(tpl, "/") { + tpl = tpl[:len(tpl)-1] + endSlash = true + } + varsN := make([]string, len(idxs)/2) + varsR := make([]*regexp.Regexp, len(idxs)/2) + pattern := bytes.NewBufferString("") + if !matchQuery { + pattern.WriteByte('^') + } + reverse := bytes.NewBufferString("") + var end int + var err error + for i := 0; i < len(idxs); i += 2 { + // Set all values we are interested in. + raw := tpl[end:idxs[i]] + end = idxs[i+1] + parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) + name := parts[0] + patt := defaultPattern + if len(parts) == 2 { + patt = parts[1] + } + // Name or pattern can't be empty. + if name == "" || patt == "" { + return nil, fmt.Errorf("mux: missing name or pattern in %q", + tpl[idxs[i]:end]) + } + // Build the regexp pattern. + fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) + // Build the reverse template. + fmt.Fprintf(reverse, "%s%%s", raw) + // Append variable name and compiled pattern. + varsN[i/2] = name + varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) + if err != nil { + return nil, err + } + } + // Add the remaining. + raw := tpl[end:] + pattern.WriteString(regexp.QuoteMeta(raw)) + if strictSlash { + pattern.WriteString("[/]?") + } + if !matchPrefix { + pattern.WriteByte('$') + } + reverse.WriteString(raw) + if endSlash { + reverse.WriteByte('/') + } + // Compile full regexp. + reg, errCompile := regexp.Compile(pattern.String()) + if errCompile != nil { + return nil, errCompile + } + // Done! + return &routeRegexp{ + template: template, + matchHost: matchHost, + matchQuery: matchQuery, + strictSlash: strictSlash, + regexp: reg, + reverse: reverse.String(), + varsN: varsN, + varsR: varsR, + }, nil +} + +// routeRegexp stores a regexp to match a host or path and information to +// collect and validate route variables. +type routeRegexp struct { + // The unmodified template. + template string + // True for host match, false for path or query string match. + matchHost bool + // True for query string match, false for path and host match. + matchQuery bool + // The strictSlash value defined on the route, but disabled if PathPrefix was used. + strictSlash bool + // Expanded regexp. + regexp *regexp.Regexp + // Reverse template. + reverse string + // Variable names. + varsN []string + // Variable regexps (validators). + varsR []*regexp.Regexp +} + +// Match matches the regexp against the URL host or path. +func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { + if !r.matchHost { + if r.matchQuery { + return r.regexp.MatchString(req.URL.RawQuery) + } else { + return r.regexp.MatchString(req.URL.Path) + } + } + return r.regexp.MatchString(getHost(req)) +} + +// url builds a URL part using the given values. +func (r *routeRegexp) url(values map[string]string) (string, error) { + urlValues := make([]interface{}, len(r.varsN)) + for k, v := range r.varsN { + value, ok := values[v] + if !ok { + return "", fmt.Errorf("mux: missing route variable %q", v) + } + urlValues[k] = value + } + rv := fmt.Sprintf(r.reverse, urlValues...) + if !r.regexp.MatchString(rv) { + // The URL is checked against the full regexp, instead of checking + // individual variables. This is faster but to provide a good error + // message, we check individual regexps if the URL doesn't match. + for k, v := range r.varsN { + if !r.varsR[k].MatchString(values[v]) { + return "", fmt.Errorf( + "mux: variable %q doesn't match, expected %q", values[v], + r.varsR[k].String()) + } + } + } + return rv, nil +} + +// braceIndices returns the first level curly brace indices from a string. +// It returns an error in case of unbalanced braces. +func braceIndices(s string) ([]int, error) { + var level, idx int + idxs := make([]int, 0) + for i := 0; i < len(s); i++ { + switch s[i] { + case '{': + if level++; level == 1 { + idx = i + } + case '}': + if level--; level == 0 { + idxs = append(idxs, idx, i+1) + } else if level < 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + } + } + if level != 0 { + return nil, fmt.Errorf("mux: unbalanced braces in %q", s) + } + return idxs, nil +} + +// ---------------------------------------------------------------------------- +// routeRegexpGroup +// ---------------------------------------------------------------------------- + +// routeRegexpGroup groups the route matchers that carry variables. +type routeRegexpGroup struct { + host *routeRegexp + path *routeRegexp + queries []*routeRegexp +} + +// setMatch extracts the variables from the URL once a route matches. +func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { + // Store host variables. + if v.host != nil { + hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) + if hostVars != nil { + for k, v := range v.host.varsN { + m.Vars[v] = hostVars[k+1] + } + } + } + // Store path variables. + if v.path != nil { + pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) + if pathVars != nil { + for k, v := range v.path.varsN { + m.Vars[v] = pathVars[k+1] + } + // Check if we should redirect. + if v.path.strictSlash { + p1 := strings.HasSuffix(req.URL.Path, "/") + p2 := strings.HasSuffix(v.path.template, "/") + if p1 != p2 { + u, _ := url.Parse(req.URL.String()) + if p1 { + u.Path = u.Path[:len(u.Path)-1] + } else { + u.Path += "/" + } + m.Handler = http.RedirectHandler(u.String(), 301) + } + } + } + } + // Store query string variables. + rawQuery := req.URL.RawQuery + for _, q := range v.queries { + queryVars := q.regexp.FindStringSubmatch(rawQuery) + if queryVars != nil { + for k, v := range q.varsN { + m.Vars[v] = queryVars[k+1] + } + } + } +} + +// getHost tries its best to return the request host. +func getHost(r *http.Request) string { + if r.URL.IsAbs() { + return r.URL.Host + } + host := r.Host + // Slice off any port information. + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + return host + +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/route.go b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/route.go new file mode 100644 index 0000000000..d4f0146885 --- /dev/null +++ b/libnetwork/Godeps/_workspace/src/github.com/gorilla/mux/route.go @@ -0,0 +1,571 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mux + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strings" +) + +// Route stores information to match a request and build URLs. +type Route struct { + // Parent where the route was registered (a Router). + parent parentRoute + // Request handler for the route. + handler http.Handler + // List of matchers. + matchers []matcher + // Manager for the variables from host and path. + regexp *routeRegexpGroup + // If true, when the path pattern is "/path/", accessing "/path" will + // redirect to the former and vice versa. + strictSlash bool + // If true, this route never matches: it is only used to build URLs. + buildOnly bool + // The name used to build URLs. + name string + // Error resulted from building a route. + err error + + buildVarsFunc BuildVarsFunc +} + +// Match matches the route against the request. +func (r *Route) Match(req *http.Request, match *RouteMatch) bool { + if r.buildOnly || r.err != nil { + return false + } + // Match everything. + for _, m := range r.matchers { + if matched := m.Match(req, match); !matched { + return false + } + } + // Yay, we have a match. Let's collect some info about it. + if match.Route == nil { + match.Route = r + } + if match.Handler == nil { + match.Handler = r.handler + } + if match.Vars == nil { + match.Vars = make(map[string]string) + } + // Set variables. + if r.regexp != nil { + r.regexp.setMatch(req, match, r) + } + return true +} + +// ---------------------------------------------------------------------------- +// Route attributes +// ---------------------------------------------------------------------------- + +// GetError returns an error resulted from building the route, if any. +func (r *Route) GetError() error { + return r.err +} + +// BuildOnly sets the route to never match: it is only used to build URLs. +func (r *Route) BuildOnly() *Route { + r.buildOnly = true + return r +} + +// Handler -------------------------------------------------------------------- + +// Handler sets a handler for the route. +func (r *Route) Handler(handler http.Handler) *Route { + if r.err == nil { + r.handler = handler + } + return r +} + +// HandlerFunc sets a handler function for the route. +func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { + return r.Handler(http.HandlerFunc(f)) +} + +// GetHandler returns the handler for the route, if any. +func (r *Route) GetHandler() http.Handler { + return r.handler +} + +// Name ----------------------------------------------------------------------- + +// Name sets the name for the route, used to build URLs. +// If the name was registered already it will be overwritten. +func (r *Route) Name(name string) *Route { + if r.name != "" { + r.err = fmt.Errorf("mux: route already has name %q, can't set %q", + r.name, name) + } + if r.err == nil { + r.name = name + r.getNamedRoutes()[name] = r + } + return r +} + +// GetName returns the name for the route, if any. +func (r *Route) GetName() string { + return r.name +} + +// ---------------------------------------------------------------------------- +// Matchers +// ---------------------------------------------------------------------------- + +// matcher types try to match a request. +type matcher interface { + Match(*http.Request, *RouteMatch) bool +} + +// addMatcher adds a matcher to the route. +func (r *Route) addMatcher(m matcher) *Route { + if r.err == nil { + r.matchers = append(r.matchers, m) + } + return r +} + +// addRegexpMatcher adds a host or path matcher and builder to a route. +func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { + if r.err != nil { + return r.err + } + r.regexp = r.getRegexpGroup() + if !matchHost && !matchQuery { + if len(tpl) == 0 || tpl[0] != '/' { + return fmt.Errorf("mux: path must start with a slash, got %q", tpl) + } + if r.regexp.path != nil { + tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl + } + } + rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) + if err != nil { + return err + } + for _, q := range r.regexp.queries { + if err = uniqueVars(rr.varsN, q.varsN); err != nil { + return err + } + } + if matchHost { + if r.regexp.path != nil { + if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { + return err + } + } + r.regexp.host = rr + } else { + if r.regexp.host != nil { + if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { + return err + } + } + if matchQuery { + r.regexp.queries = append(r.regexp.queries, rr) + } else { + r.regexp.path = rr + } + } + r.addMatcher(rr) + return nil +} + +// Headers -------------------------------------------------------------------- + +// headerMatcher matches the request against header values. +type headerMatcher map[string]string + +func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchMap(m, r.Header, true) +} + +// Headers adds a matcher for request header values. +// It accepts a sequence of key/value pairs to be matched. For example: +// +// r := mux.NewRouter() +// r.Headers("Content-Type", "application/json", +// "X-Requested-With", "XMLHttpRequest") +// +// The above route will only match if both request header values match. +// +// It the value is an empty string, it will match any value if the key is set. +func (r *Route) Headers(pairs ...string) *Route { + if r.err == nil { + var headers map[string]string + headers, r.err = mapFromPairs(pairs...) + return r.addMatcher(headerMatcher(headers)) + } + return r +} + +// Host ----------------------------------------------------------------------- + +// Host adds a matcher for the URL host. +// It accepts a template with zero or more URL variables enclosed by {}. +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next dot. +// +// - {name:pattern} matches the given regexp pattern. +// +// For example: +// +// r := mux.NewRouter() +// r.Host("www.domain.com") +// r.Host("{subdomain}.domain.com") +// r.Host("{subdomain:[a-z]+}.domain.com") +// +// Variable names must be unique in a given route. They can be retrieved +// calling mux.Vars(request). +func (r *Route) Host(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, true, false, false) + return r +} + +// MatcherFunc ---------------------------------------------------------------- + +// MatcherFunc is the function signature used by custom matchers. +type MatcherFunc func(*http.Request, *RouteMatch) bool + +func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { + return m(r, match) +} + +// MatcherFunc adds a custom function to be used as request matcher. +func (r *Route) MatcherFunc(f MatcherFunc) *Route { + return r.addMatcher(f) +} + +// Methods -------------------------------------------------------------------- + +// methodMatcher matches the request against HTTP methods. +type methodMatcher []string + +func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchInArray(m, r.Method) +} + +// Methods adds a matcher for HTTP methods. +// It accepts a sequence of one or more methods to be matched, e.g.: +// "GET", "POST", "PUT". +func (r *Route) Methods(methods ...string) *Route { + for k, v := range methods { + methods[k] = strings.ToUpper(v) + } + return r.addMatcher(methodMatcher(methods)) +} + +// Path ----------------------------------------------------------------------- + +// Path adds a matcher for the URL path. +// It accepts a template with zero or more URL variables enclosed by {}. The +// template must start with a "/". +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. +// +// For example: +// +// r := mux.NewRouter() +// r.Path("/products/").Handler(ProductsHandler) +// r.Path("/products/{key}").Handler(ProductsHandler) +// r.Path("/articles/{category}/{id:[0-9]+}"). +// Handler(ArticleHandler) +// +// Variable names must be unique in a given route. They can be retrieved +// calling mux.Vars(request). +func (r *Route) Path(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, false, false, false) + return r +} + +// PathPrefix ----------------------------------------------------------------- + +// PathPrefix adds a matcher for the URL path prefix. This matches if the given +// template is a prefix of the full URL path. See Route.Path() for details on +// the tpl argument. +// +// Note that it does not treat slashes specially ("/foobar/" will be matched by +// the prefix "/foo") so you may want to use a trailing slash here. +// +// Also note that the setting of Router.StrictSlash() has no effect on routes +// with a PathPrefix matcher. +func (r *Route) PathPrefix(tpl string) *Route { + r.err = r.addRegexpMatcher(tpl, false, true, false) + return r +} + +// Query ---------------------------------------------------------------------- + +// Queries adds a matcher for URL query values. +// It accepts a sequence of key/value pairs. Values may define variables. +// For example: +// +// r := mux.NewRouter() +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") +// +// The above route will only match if the URL contains the defined queries +// values, e.g.: ?foo=bar&id=42. +// +// It the value is an empty string, it will match any value if the key is set. +// +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. +func (r *Route) Queries(pairs ...string) *Route { + length := len(pairs) + if length%2 != 0 { + r.err = fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + return nil + } + for i := 0; i < length; i += 2 { + if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { + return r + } + } + + return r +} + +// Schemes -------------------------------------------------------------------- + +// schemeMatcher matches the request against URL schemes. +type schemeMatcher []string + +func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { + return matchInArray(m, r.URL.Scheme) +} + +// Schemes adds a matcher for URL schemes. +// It accepts a sequence of schemes to be matched, e.g.: "http", "https". +func (r *Route) Schemes(schemes ...string) *Route { + for k, v := range schemes { + schemes[k] = strings.ToLower(v) + } + return r.addMatcher(schemeMatcher(schemes)) +} + +// BuildVarsFunc -------------------------------------------------------------- + +// BuildVarsFunc is the function signature used by custom build variable +// functions (which can modify route variables before a route's URL is built). +type BuildVarsFunc func(map[string]string) map[string]string + +// BuildVarsFunc adds a custom function to be used to modify build variables +// before a route's URL is built. +func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { + r.buildVarsFunc = f + return r +} + +// Subrouter ------------------------------------------------------------------ + +// Subrouter creates a subrouter for the route. +// +// It will test the inner routes only if the parent route matched. For example: +// +// r := mux.NewRouter() +// s := r.Host("www.domain.com").Subrouter() +// s.HandleFunc("/products/", ProductsHandler) +// s.HandleFunc("/products/{key}", ProductHandler) +// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) +// +// Here, the routes registered in the subrouter won't be tested if the host +// doesn't match. +func (r *Route) Subrouter() *Router { + router := &Router{parent: r, strictSlash: r.strictSlash} + r.addMatcher(router) + return router +} + +// ---------------------------------------------------------------------------- +// URL building +// ---------------------------------------------------------------------------- + +// URL builds a URL for the route. +// +// It accepts a sequence of key/value pairs for the route variables. For +// example, given this route: +// +// r := mux.NewRouter() +// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") +// +// ...a URL for it can be built using: +// +// url, err := r.Get("article").URL("category", "technology", "id", "42") +// +// ...which will return an url.URL with the following path: +// +// "/articles/technology/42" +// +// This also works for host variables: +// +// r := mux.NewRouter() +// r.Host("{subdomain}.domain.com"). +// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). +// Name("article") +// +// // url.String() will be "http://news.domain.com/articles/technology/42" +// url, err := r.Get("article").URL("subdomain", "news", +// "category", "technology", +// "id", "42") +// +// All variables defined in the route are required, and their values must +// conform to the corresponding patterns. +func (r *Route) URL(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil { + return nil, errors.New("mux: route doesn't have a host or path") + } + values, err := r.prepareVars(pairs...) + if err != nil { + return nil, err + } + var scheme, host, path string + if r.regexp.host != nil { + // Set a default scheme. + scheme = "http" + if host, err = r.regexp.host.url(values); err != nil { + return nil, err + } + } + if r.regexp.path != nil { + if path, err = r.regexp.path.url(values); err != nil { + return nil, err + } + } + return &url.URL{ + Scheme: scheme, + Host: host, + Path: path, + }, nil +} + +// URLHost builds the host part of the URL for a route. See Route.URL(). +// +// The route must have a host defined. +func (r *Route) URLHost(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.host == nil { + return nil, errors.New("mux: route doesn't have a host") + } + values, err := r.prepareVars(pairs...) + if err != nil { + return nil, err + } + host, err := r.regexp.host.url(values) + if err != nil { + return nil, err + } + return &url.URL{ + Scheme: "http", + Host: host, + }, nil +} + +// URLPath builds the path part of the URL for a route. See Route.URL(). +// +// The route must have a path defined. +func (r *Route) URLPath(pairs ...string) (*url.URL, error) { + if r.err != nil { + return nil, r.err + } + if r.regexp == nil || r.regexp.path == nil { + return nil, errors.New("mux: route doesn't have a path") + } + values, err := r.prepareVars(pairs...) + if err != nil { + return nil, err + } + path, err := r.regexp.path.url(values) + if err != nil { + return nil, err + } + return &url.URL{ + Path: path, + }, nil +} + +// prepareVars converts the route variable pairs into a map. If the route has a +// BuildVarsFunc, it is invoked. +func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { + m, err := mapFromPairs(pairs...) + if err != nil { + return nil, err + } + return r.buildVars(m), nil +} + +func (r *Route) buildVars(m map[string]string) map[string]string { + if r.parent != nil { + m = r.parent.buildVars(m) + } + if r.buildVarsFunc != nil { + m = r.buildVarsFunc(m) + } + return m +} + +// ---------------------------------------------------------------------------- +// parentRoute +// ---------------------------------------------------------------------------- + +// parentRoute allows routes to know about parent host and path definitions. +type parentRoute interface { + getNamedRoutes() map[string]*Route + getRegexpGroup() *routeRegexpGroup + buildVars(map[string]string) map[string]string +} + +// getNamedRoutes returns the map where named routes are registered. +func (r *Route) getNamedRoutes() map[string]*Route { + if r.parent == nil { + // During tests router is not always set. + r.parent = NewRouter() + } + return r.parent.getNamedRoutes() +} + +// getRegexpGroup returns regexp definitions from this route. +func (r *Route) getRegexpGroup() *routeRegexpGroup { + if r.regexp == nil { + if r.parent == nil { + // During tests router is not always set. + r.parent = NewRouter() + } + regexp := r.parent.getRegexpGroup() + if regexp == nil { + r.regexp = new(routeRegexpGroup) + } else { + // Copy. + r.regexp = &routeRegexpGroup{ + host: regexp.host, + path: regexp.path, + queries: regexp.queries, + } + } + } + return r.regexp +} diff --git a/libnetwork/api/api.go b/libnetwork/api/api.go new file mode 100644 index 0000000000..8785901b38 --- /dev/null +++ b/libnetwork/api/api.go @@ -0,0 +1,546 @@ +package api + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/sandbox" + "github.com/gorilla/mux" +) + +var ( + successResponse = responseStatus{Status: "Success", StatusCode: http.StatusOK} + createdResponse = responseStatus{Status: "Created", StatusCode: http.StatusCreated} + mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest} +) + +const ( + urlNwName = "name" + urlNwID = "id" + urlEpName = "endpoint-name" + urlEpID = "endpoint-id" + urlCnID = "container-id" +) + +// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork +func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) { + h := &httpHandler{c: c} + h.initRouter() + return h.handleRequest +} + +type responseStatus struct { + Status string + StatusCode int +} + +func (r *responseStatus) isOK() bool { + return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated +} + +type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) + +type httpHandler struct { + c libnetwork.NetworkController + r *mux.Router +} + +func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) { + // Make sure the service is there + if h.c == nil { + http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable) + return + } + + // Get handler from router and execute it + h.r.ServeHTTP(w, req) +} + +func (h *httpHandler) initRouter() { + m := map[string]map[string]processor{ + "GET": { + "/networks": procGetNetworks, + "/networks/name/{" + urlNwName + ":.*}": procGetNetwork, + "/networks/id/{" + urlNwID + ":.*}": procGetNetwork, + "/networks/name/{" + urlNwName + ":.*}/endpoints/": procGetEndpoints, + "/networks/id/{" + urlNwID + ":.*}/endpoints/": procGetEndpoints, + "/networks/name/{" + urlNwName + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procGetEndpoint, + "/networks/id/{" + urlNwName + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procGetEndpoint, + "/networks/name/{" + urlNwName + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procGetEndpoint, + "/networks/id/{" + urlNwID + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procGetEndpoint, + }, + "POST": { + "/networks/name/{" + urlNwName + ":.*}": procCreateNetwork, + "/networks/name/{" + urlNwName + ":.*}/endpoint/name/{" + urlEpName + ":.*}": procCreateEndpoint, + "/networks/name/{" + urlNwName + ":.*}/endpoint/name/{" + urlEpName + ":.*}/container/{" + urlCnID + ":.*}": procJoinEndpoint, + }, + "DELETE": { + "/networks/name/{" + urlNwName + ":.*}": procDeleteNetwork, + "/networks/id/{" + urlNwID + ":.*}": procDeleteNetwork, + "/networks/name/{" + urlNwName + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procDeleteEndpoint, + "/networks/name/{" + urlNwName + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procDeleteEndpoint, + "/networks/id/{" + urlNwID + ":.*}/endpoints/name/{" + urlEpName + ":.*}": procDeleteEndpoint, + "/networks/id/{" + urlNwID + ":.*}/endpoints/id/{" + urlEpID + ":.*}": procDeleteEndpoint, + "/networks/name/{" + urlNwName + ":.*}/endpoint/name/{" + urlEpName + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint, + "/networks/name/{" + urlNwName + ":.*}/endpoint/id/{" + urlEpID + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint, + "/networks/id/{" + urlNwID + ":.*}/endpoint/name/{" + urlEpName + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint, + "/networks/id/{" + urlNwID + ":.*}/endpoint/id/{" + urlEpID + ":.*}/container/{" + urlCnID + ":.*}": procLeaveEndpoint, + }, + } + + h.r = mux.NewRouter() + for method, routes := range m { + for route, fct := range routes { + f := makeHandler(h.c, fct) + h.r.Path(route).Methods(method).HandlerFunc(f) + } + } +} + +func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + var ( + body []byte + err error + ) + if req.Body != nil { + body, err = ioutil.ReadAll(req.Body) + if err != nil { + http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest) + return + } + } + + res, rsp := fct(ctrl, mux.Vars(req), body) + if !rsp.isOK() { + http.Error(w, rsp.Status, rsp.StatusCode) + return + } + if res != nil { + writeJSON(w, rsp.StatusCode, res) + } + } +} + +/*********** + Resources +************/ + +// networkResource is the body of the "get network" http response message +type networkResource struct { + Name string + ID string + Type string + Endpoints []*endpointResource +} + +// endpointResource is the body of the "get endpoint" http response message +type endpointResource struct { + Name string + ID string + Network string + Info sandbox.Info +} + +func buildNetworkResource(nw libnetwork.Network) *networkResource { + r := &networkResource{} + if nw != nil { + r.Name = nw.Name() + r.ID = nw.ID() + r.Type = nw.Type() + epl := nw.Endpoints() + r.Endpoints = make([]*endpointResource, 0, len(epl)) + for _, e := range epl { + epr := buildEndpointResource(e) + r.Endpoints = append(r.Endpoints, epr) + } + } + return r +} + +func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource { + r := &endpointResource{} + if ep != nil { + r.Name = ep.Name() + r.ID = ep.ID() + r.Network = ep.Network() + + i := ep.SandboxInfo() + if i != nil { + r.Info = *i + } + } + return r +} + +/*********** + Body types +************/ + +// networkCreate is the expected body of the "create network" http request message +type networkCreate struct { + Name string + NetworkType string + Options map[string]interface{} +} + +// endpointCreate represents the body of the "create endpoint" http request message +type endpointCreate struct { + Name string + NetworkID string + ExposedPorts []netutils.TransportPort + PortMapping []netutils.PortBinding +} + +// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages +type endpointJoin struct { + ContainerID string + HostName string + DomainName string + HostsPath string + ResolvConfPath string + DNS []string + ExtraHosts []endpointExtraHost + ParentUpdates []endpointParentUpdate + UseDefaultSandbox bool +} + +// EndpointExtraHost represents the extra host object +type endpointExtraHost struct { + Name string + Address string +} + +// EndpointParentUpdate is the object carrying the information about the +// endpoint parent that needs to be updated +type endpointParentUpdate struct { + EndpointID string + Name string + Address string +} + +func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption { + var setFctList []libnetwork.EndpointOption + if ej.HostName != "" { + setFctList = append(setFctList, libnetwork.JoinOptionHostname(ej.HostName)) + } + if ej.DomainName != "" { + setFctList = append(setFctList, libnetwork.JoinOptionDomainname(ej.DomainName)) + } + if ej.HostsPath != "" { + setFctList = append(setFctList, libnetwork.JoinOptionHostsPath(ej.HostsPath)) + } + if ej.ResolvConfPath != "" { + setFctList = append(setFctList, libnetwork.JoinOptionResolvConfPath(ej.ResolvConfPath)) + } + if ej.UseDefaultSandbox { + setFctList = append(setFctList, libnetwork.JoinOptionUseDefaultSandbox()) + } + if ej.DNS != nil { + for _, d := range ej.DNS { + setFctList = append(setFctList, libnetwork.JoinOptionDNS(d)) + } + } + if ej.ExtraHosts != nil { + for _, e := range ej.ExtraHosts { + setFctList = append(setFctList, libnetwork.JoinOptionExtraHost(e.Name, e.Address)) + } + } + if ej.ParentUpdates != nil { + for _, p := range ej.ParentUpdates { + setFctList = append(setFctList, libnetwork.JoinOptionParentUpdate(p.EndpointID, p.Name, p.Address)) + } + } + return setFctList +} + +/****************** + Process functions +*******************/ + +/*************************** + NetworkController interface +****************************/ +func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + var create networkCreate + + err := json.Unmarshal(body, &create) + if err != nil { + return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} + } + + name := vars[urlNwName] + if name != create.Name { + return "", &mismatchResponse + } + + nw, err := c.NewNetwork(create.NetworkType, name, nil) + if err != nil { + return "", convertNetworkError(err) + } + + return nw.ID(), &createdResponse +} + +func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + t, by := detectNetworkTarget(vars) + nw, errRsp := findNetwork(c, t, by) + if !errRsp.isOK() { + return nil, errRsp + } + return buildNetworkResource(nw), &successResponse +} + +func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + var list []*networkResource + for _, nw := range c.Networks() { + nwr := buildNetworkResource(nw) + list = append(list, nwr) + } + return list, &successResponse +} + +/****************** + Network interface +*******************/ +func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + var ec endpointCreate + + err := json.Unmarshal(body, &ec) + if err != nil { + return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} + } + + epn := vars[urlEpName] + if ec.Name != epn { + return "", &mismatchResponse + } + + nwT, nwBy := detectNetworkTarget(vars) + n, errRsp := findNetwork(c, nwT, nwBy) + if !errRsp.isOK() { + return "", errRsp + } + + if ec.NetworkID != n.ID() { + return "", &mismatchResponse + } + + var setFctList []libnetwork.EndpointOption + if ec.ExposedPorts != nil { + setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts)) + } + if ec.PortMapping != nil { + setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping)) + } + + ep, err := n.CreateEndpoint(epn, setFctList...) + if err != nil { + return "", convertNetworkError(err) + } + + return ep.ID(), &createdResponse +} + +func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + nwT, nwBy := detectNetworkTarget(vars) + epT, epBy := detectEndpointTarget(vars) + + ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) + if !errRsp.isOK() { + return nil, errRsp + } + + return buildEndpointResource(ep), &successResponse +} + +func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + target, by := detectNetworkTarget(vars) + + nw, errRsp := findNetwork(c, target, by) + if !errRsp.isOK() { + return nil, errRsp + } + + var list []*endpointResource + for _, ep := range nw.Endpoints() { + epr := buildEndpointResource(ep) + list = append(list, epr) + } + + return list, &successResponse +} + +func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + target, by := detectNetworkTarget(vars) + + nw, errRsp := findNetwork(c, target, by) + if !errRsp.isOK() { + return nil, errRsp + } + + err := nw.Delete() + if err != nil { + return nil, convertNetworkError(err) + } + + return nil, &successResponse +} + +/****************** + Endpoint interface +*******************/ +func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + var ej endpointJoin + err := json.Unmarshal(body, &ej) + if err != nil { + return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} + } + + cid := vars[urlCnID] + if ej.ContainerID != cid { + return "", &mismatchResponse + } + + nwT, nwBy := detectNetworkTarget(vars) + epT, epBy := detectEndpointTarget(vars) + + ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) + if !errRsp.isOK() { + return nil, errRsp + } + + cd, err := ep.Join(ej.ContainerID, ej.parseOptions()...) + if err != nil { + return nil, convertNetworkError(err) + } + return cd, &successResponse +} + +func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + nwT, nwBy := detectNetworkTarget(vars) + epT, epBy := detectEndpointTarget(vars) + + ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) + if !errRsp.isOK() { + return nil, errRsp + } + + err := ep.Leave(vars[urlCnID], nil) + if err != nil { + return nil, convertNetworkError(err) + } + + return nil, &successResponse +} + +func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { + nwT, nwBy := detectNetworkTarget(vars) + epT, epBy := detectEndpointTarget(vars) + + ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) + if !errRsp.isOK() { + return nil, errRsp + } + + err := ep.Delete() + if err != nil { + return nil, convertNetworkError(err) + } + + return nil, &successResponse +} + +/*********** + Utilities +************/ +const ( + byID = iota + byName +) + +func detectNetworkTarget(vars map[string]string) (string, int) { + if target, ok := vars[urlNwName]; ok { + return target, byName + } + if target, ok := vars[urlNwID]; ok { + return target, byID + } + // vars are populated from the URL, following cannot happen + panic("Missing URL variable parameter for network") +} + +func detectEndpointTarget(vars map[string]string) (string, int) { + if target, ok := vars[urlEpName]; ok { + return target, byName + } + if target, ok := vars[urlEpID]; ok { + return target, byID + } + // vars are populated from the URL, following cannot happen + panic("Missing URL variable parameter for endpoint") +} + +func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) { + var ( + nw libnetwork.Network + err error + ) + switch by { + case byID: + nw, err = c.NetworkByID(s) + case byName: + nw, err = c.NetworkByName(s) + default: + panic(fmt.Sprintf("unexpected selector for network search: %d", by)) + } + if err != nil { + return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest} + } + if nw == nil { + return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound} + } + return nw, &successResponse +} + +func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) { + nw, errRsp := findNetwork(c, ns, nwBy) + if !errRsp.isOK() { + return nil, errRsp + } + var ( + err error + ep libnetwork.Endpoint + ) + switch epBy { + case byID: + ep, err = nw.EndpointByID(es) + case byName: + ep, err = nw.EndpointByName(es) + default: + panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy)) + } + if err != nil { + return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest} + } + if ep == nil { + return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound} + } + return ep, &successResponse +} + +func convertNetworkError(err error) *responseStatus { + // No real libnetwork error => http error code conversion for now. + // Will came in later when new interface for libnetwork error is vailable + return &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest} +} + +func writeJSON(w http.ResponseWriter, code int, v interface{}) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + return json.NewEncoder(w).Encode(v) +} diff --git a/libnetwork/api/api_test.go b/libnetwork/api/api_test.go new file mode 100644 index 0000000000..eb71fb7f40 --- /dev/null +++ b/libnetwork/api/api_test.go @@ -0,0 +1,1306 @@ +package api + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "runtime" + "testing" + + "github.com/docker/docker/pkg/reexec" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/pkg/netlabel" + "github.com/docker/libnetwork/pkg/options" +) + +const ( + bridgeNetType = "bridge" + bridgeName = "docker0" +) + +func getEmptyGenericOption() map[string]interface{} { + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = options.Generic{} + return genericOption +} + +func i2s(i interface{}) string { + s, ok := i.(string) + if !ok { + panic(fmt.Sprintf("Failed i2s for %v", i)) + } + return s +} + +func i2e(i interface{}) *endpointResource { + s, ok := i.(*endpointResource) + if !ok { + panic(fmt.Sprintf("Failed i2e for %v", i)) + } + return s +} + +func i2c(i interface{}) *libnetwork.ContainerData { + s, ok := i.(*libnetwork.ContainerData) + if !ok { + panic(fmt.Sprintf("Failed i2c for %v", i)) + } + return s +} + +func i2eL(i interface{}) []*endpointResource { + s, ok := i.([]*endpointResource) + if !ok { + panic(fmt.Sprintf("Failed i2eL for %v", i)) + } + return s +} + +func i2n(i interface{}) *networkResource { + s, ok := i.(*networkResource) + if !ok { + panic(fmt.Sprintf("Failed i2n for %v", i)) + } + return s +} + +func i2nL(i interface{}) []*networkResource { + s, ok := i.([]*networkResource) + if !ok { + panic(fmt.Sprintf("Failed i2nL for %v", i)) + } + return s +} + +func TestMain(m *testing.M) { + if reexec.Init() { + return + } + os.Exit(m.Run()) +} + +func TestJoinOptionParser(t *testing.T) { + hn := "host1" + dn := "docker.com" + hp := "/etc/hosts" + rc := "/etc/resolv.conf" + dnss := []string{"8.8.8.8", "172.28.34.5"} + ehs := []endpointExtraHost{endpointExtraHost{Name: "extra1", Address: "172.28.9.1"}, endpointExtraHost{Name: "extra2", Address: "172.28.9.2"}} + pus := []endpointParentUpdate{endpointParentUpdate{EndpointID: "abc123def456", Name: "serv1", Address: "172.28.30.123"}} + + ej := endpointJoin{ + HostName: hn, + DomainName: dn, + HostsPath: hp, + ResolvConfPath: rc, + DNS: dnss, + ExtraHosts: ehs, + ParentUpdates: pus, + UseDefaultSandbox: true, + } + + if len(ej.parseOptions()) != 10 { + t.Fatalf("Failed to generate all libnetwork.EndpointJoinOption methods libnetwork.EndpointJoinOption method") + } + +} + +func TestJson(t *testing.T) { + nc := networkCreate{Name: "mynet", NetworkType: bridgeNetType} + b, err := json.Marshal(nc) + if err != nil { + t.Fatal(err) + } + + var ncp networkCreate + err = json.Unmarshal(b, &ncp) + if err != nil { + t.Fatal(err) + } + + if nc.Name != ncp.Name || nc.NetworkType != ncp.NetworkType { + t.Fatalf("Incorrect networkCreate after json encoding/deconding: %v", ncp) + } + + ec := endpointCreate{Name: "mioEp", NetworkID: "0xabcde"} + b, err = json.Marshal(ec) + if err != nil { + t.Fatal(err) + } + + var ecp endpointCreate + err = json.Unmarshal(b, &ecp) + if err != nil { + t.Fatal(err) + } + + if ec.Name != ecp.Name || ec.NetworkID != ecp.NetworkID { + t.Fatalf("Incorrect endpointCreate after json encoding/deconding: %v", ecp) + } + + jl := endpointJoin{ContainerID: "abcdef456789"} + b, err = json.Marshal(jl) + if err != nil { + t.Fatal(err) + } + + var jld endpointJoin + err = json.Unmarshal(b, &jld) + if err != nil { + t.Fatal(err) + } + + if jl.ContainerID != jld.ContainerID { + t.Fatalf("Incorrect endpointJoin after json encoding/deconding: %v", ecp) + } +} + +func TestCreateDeleteNetwork(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + badBody, err := json.Marshal("bad body") + if err != nil { + t.Fatal(err) + } + + goodVars := map[string]string{urlNwName: "myNet"} + _, errRsp := procCreateNetwork(c, goodVars, badBody) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + + incompleteBody, err := json.Marshal(networkCreate{Name: "myNet"}) + if err != nil { + t.Fatal(err) + } + + _, errRsp = procCreateNetwork(c, goodVars, incompleteBody) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp.StatusCode) + } + + ops := make(map[string]interface{}) + ops[netlabel.GenericData] = options.Generic{} + nc := networkCreate{Name: "myNet", NetworkType: bridgeNetType, Options: ops} + goodBody, err := json.Marshal(nc) + if err != nil { + t.Fatal(err) + } + + badVars := map[string]string{urlNwName: ""} + _, errRsp = procCreateNetwork(c, badVars, goodBody) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp.StatusCode) + } + + badVars[urlNwName] = "badNetworkName" + _, errRsp = procCreateNetwork(c, badVars, goodBody) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp.StatusCode) + } + + _, errRsp = procCreateNetwork(c, goodVars, goodBody) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + _, errRsp = procDeleteNetwork(c, badVars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected to fail but succeeded") + } + + badVars[urlNwName] = "" + _, errRsp = procDeleteNetwork(c, badVars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected to fail but succeeded") + } + + _, errRsp = procDeleteNetwork(c, goodVars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } +} + +func TestGetNetworksAndEndpoints(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + nc := networkCreate{Name: "sh", NetworkType: bridgeNetType} + body, err := json.Marshal(nc) + if err != nil { + t.Fatal(err) + } + + vars := map[string]string{urlNwName: "sh"} + inid, errRsp := procCreateNetwork(c, vars, body) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + nid, ok := inid.(string) + if !ok { + t.FailNow() + } + + ec1 := endpointCreate{ + Name: "ep1", + NetworkID: string(nid), + ExposedPorts: []netutils.TransportPort{ + netutils.TransportPort{Proto: netutils.TCP, Port: uint16(5000)}, + netutils.TransportPort{Proto: netutils.UDP, Port: uint16(400)}, + netutils.TransportPort{Proto: netutils.TCP, Port: uint16(600)}, + }, + PortMapping: []netutils.PortBinding{ + netutils.PortBinding{Proto: netutils.TCP, Port: uint16(230), HostPort: uint16(23000)}, + netutils.PortBinding{Proto: netutils.UDP, Port: uint16(200), HostPort: uint16(22000)}, + netutils.PortBinding{Proto: netutils.TCP, Port: uint16(120), HostPort: uint16(12000)}, + }, + } + b1, err := json.Marshal(ec1) + if err != nil { + t.Fatal(err) + } + ec2 := endpointCreate{Name: "ep2", NetworkID: nid} + b2, err := json.Marshal(ec2) + if err != nil { + t.Fatal(err) + } + + vars[urlEpName] = "ep1" + ieid1, errRsp := procCreateEndpoint(c, vars, b1) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + eid1 := i2s(ieid1) + vars[urlEpName] = "ep2" + ieid2, errRsp := procCreateEndpoint(c, vars, b2) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + eid2 := i2s(ieid2) + + vars[urlNwName] = "" + vars[urlEpName] = "ep1" + _, errRsp = procGetEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure but succeeded: %v", errRsp) + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode) + } + + vars = make(map[string]string) + vars[urlNwName] = "sh" + vars[urlEpID] = "" + _, errRsp = procGetEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure but succeeded: %v", errRsp) + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode) + } + + vars = make(map[string]string) + vars[urlNwID] = "" + vars[urlEpID] = eid1 + _, errRsp = procGetEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure but succeeded: %v", errRsp) + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected to fail with http.StatusBadRequest, but got: %d", errRsp.StatusCode) + } + + // nw by name and ep by id + vars[urlNwName] = "sh" + i1, errRsp := procGetEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + // nw by name and ep by name + delete(vars, urlEpID) + vars[urlEpName] = "ep1" + i2, errRsp := procGetEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + // nw by id and ep by name + delete(vars, urlNwName) + vars[urlNwID] = nid + i3, errRsp := procGetEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + // nw by id and ep by id + delete(vars, urlEpName) + vars[urlEpID] = eid1 + i4, errRsp := procGetEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + id1 := i2e(i1).ID + if id1 != i2e(i2).ID || id1 != i2e(i3).ID || id1 != i2e(i4).ID { + t.Fatalf("Endpoints retireved via different query parameters differ: %v, %v, %v, %v", i1, i2, i3, i4) + } + + vars[urlNwName] = "" + _, errRsp = procGetEndpoints(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + delete(vars, urlNwName) + vars[urlNwID] = "fakeID" + _, errRsp = procGetEndpoints(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlNwID] = nid + _, errRsp = procGetEndpoints(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + vars[urlNwName] = "sh" + iepList, errRsp := procGetEndpoints(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + epList := i2eL(iepList) + if len(epList) != 2 { + t.Fatalf("Did not return the expected number (2) of endpoint resources: %d", len(epList)) + } + if "sh" != epList[0].Network || "sh" != epList[1].Network { + t.Fatalf("Did not find expected network name in endpoint resources") + } + + vars = make(map[string]string) + vars[urlNwName] = "" + _, errRsp = procGetNetwork(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Exepected failure, got: %v", errRsp) + } + vars[urlNwName] = "shhhhh" + _, errRsp = procGetNetwork(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Exepected failure, got: %v", errRsp) + } + vars[urlNwName] = "sh" + inr1, errRsp := procGetNetwork(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + nr1 := i2n(inr1) + + delete(vars, urlNwName) + vars[urlNwID] = "cacca" + _, errRsp = procGetNetwork(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + vars[urlNwID] = nid + inr2, errRsp := procGetNetwork(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("procgetNetworkByName() != procgetNetworkById(), %v vs %v", inr1, inr2) + } + nr2 := i2n(inr2) + if nr1.Name != nr2.Name || nr1.Type != nr2.Type || nr1.ID != nr2.ID || len(nr1.Endpoints) != len(nr2.Endpoints) { + t.Fatalf("Get by name and Get failure: %v", errRsp) + } + + if len(nr1.Endpoints) != 2 { + t.Fatalf("Did not find the expected number (2) of endpoint resources in the network resource: %d", len(nr1.Endpoints)) + } + for _, er := range nr1.Endpoints { + if er.ID != eid1 && er.ID != eid2 { + t.Fatalf("Did not find the expected endpoint resources in the network resource: %v", nr1.Endpoints) + } + } + + iList, errRsp := procGetNetworks(c, nil, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + netList := i2nL(iList) + if len(netList) != 1 { + t.Fatalf("Did not return the expected number of network resources") + } + if nid != netList[0].ID { + t.Fatalf("Did not find expected network %s: %v", nid, netList) + } + + _, errRsp = procDeleteNetwork(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Exepected failure, got: %v", errRsp) + } + + vars[urlEpName] = "ep1" + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + iepList, errRsp = procGetEndpoints(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + epList = i2eL(iepList) + if len(epList) != 1 { + t.Fatalf("Did not return the expected number (1) of endpoint resources: %d", len(epList)) + } + + vars[urlEpName] = "ep2" + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + iepList, errRsp = procGetEndpoints(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + epList = i2eL(iepList) + if len(epList) != 0 { + t.Fatalf("Did not return the expected number (0) of endpoint resources: %d", len(epList)) + } + + _, errRsp = procDeleteNetwork(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + iList, errRsp = procGetNetworks(c, nil, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + netList = i2nL(iList) + if len(netList) != 0 { + t.Fatalf("Did not return the expected number of network resources") + } +} + +func TestFindNetworkUtil(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + nw, err := c.NewNetwork(bridgeNetType, "network", nil) + if err != nil { + t.Fatal(err) + } + nid := nw.ID() + + defer checkPanic(t) + findNetwork(c, "", -1) + + _, errRsp := findNetwork(c, "", byName) + if errRsp == &successResponse { + t.Fatalf("Expected to fail but succeeded") + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode) + } + + n, errRsp := findNetwork(c, nid, byID) + if errRsp != &successResponse { + t.Fatalf("Unexpected failure: %v", errRsp) + } + if n == nil { + t.Fatalf("Unexpected nil libnetwork.Network") + } + if nid != n.ID() { + t.Fatalf("Incorrect libnetwork.Network resource. It has different id: %v", n) + } + if "network" != n.Name() { + t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n) + } + + n, errRsp = findNetwork(c, "network", byName) + if errRsp != &successResponse { + t.Fatalf("Unexpected failure: %v", errRsp) + } + if n == nil { + t.Fatalf("Unexpected nil libnetwork.Network") + } + if nid != n.ID() { + t.Fatalf("Incorrect libnetwork.Network resource. It has different id: %v", n) + } + if "network" != n.Name() { + t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n) + } + + n.Delete() + + _, errRsp = findNetwork(c, nid, byID) + if errRsp == &successResponse { + t.Fatalf("Expected to fail but succeeded") + } + if errRsp.StatusCode != http.StatusNotFound { + t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode) + } + + _, errRsp = findNetwork(c, "network", byName) + if errRsp == &successResponse { + t.Fatalf("Expected to fail but succeeded") + } + if errRsp.StatusCode != http.StatusNotFound { + t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode) + } +} + +func TestCreateDeleteEndpoints(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + nc := networkCreate{Name: "firstNet", NetworkType: bridgeNetType} + body, err := json.Marshal(nc) + if err != nil { + t.Fatal(err) + } + + vars := map[string]string{urlNwName: "firstNet"} + i, errRsp := procCreateNetwork(c, vars, body) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + nid := i2s(i) + + vbad, err := json.Marshal("bad endppint create data") + if err != nil { + t.Fatal(err) + } + + vars[urlEpName] = "ep1" + _, errRsp = procCreateEndpoint(c, vars, vbad) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + + bad, err := json.Marshal(endpointCreate{Name: "ep1", NetworkID: "123456"}) + if err != nil { + t.Fatal(err) + } + _, errRsp = procCreateEndpoint(c, vars, bad) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + + soso, err := json.Marshal(endpointCreate{Name: "ep11", NetworkID: nid}) + if err != nil { + t.Fatal(err) + } + _, errRsp = procCreateEndpoint(c, vars, soso) + if errRsp != &mismatchResponse { + t.Fatalf("Expected to fail with \"mismatchResponse\", but got: %v", errRsp) + } + + bla, err := json.Marshal(endpointCreate{Name: "", NetworkID: nid}) + if err != nil { + t.Fatal(err) + } + vars[urlNwName] = "firstNet" + vars[urlEpName] = "" + _, errRsp = procCreateEndpoint(c, vars, bla) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded: %v", errRsp) + } + + b, err := json.Marshal(endpointCreate{Name: "firstEp", NetworkID: nid}) + if err != nil { + t.Fatal(err) + } + + vars[urlNwName] = "secondNet" + vars[urlEpName] = "firstEp" + _, errRsp = procCreateEndpoint(c, vars, b) + if errRsp == &createdResponse { + t.Fatalf("Expected to fail but succeeded") + } + + vars[urlNwName] = "firstNet" + vars[urlEpName] = "ep1" + _, errRsp = procCreateEndpoint(c, vars, b) + if errRsp != &mismatchResponse { + t.Fatalf("Expected to fail with \"mismatchResponse\", but got: %v", errRsp) + } + + vars = make(map[string]string) + _, errRsp = procCreateEndpoint(c, vars, b) + if errRsp == &successResponse { + t.Fatalf("Expected failure but succeeded: %v", errRsp) + } + + vars[urlNwName] = "firstNet" + _, errRsp = procCreateEndpoint(c, vars, b) + if errRsp == &successResponse { + t.Fatalf("Expected failure but succeeded: %v", errRsp) + } + + vars[urlEpName] = "firstEp" + i, errRsp = procCreateEndpoint(c, vars, b) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + eid := i2s(i) + + _, errRsp = findEndpoint(c, "myNet", "firstEp", byName, byName) + if errRsp == &successResponse { + t.Fatalf("Expected failure but succeeded: %v", errRsp) + } + + ep0, errRsp := findEndpoint(c, nid, "firstEp", byID, byName) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + ep1, errRsp := findEndpoint(c, "firstNet", "firstEp", byName, byName) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + ep2, errRsp := findEndpoint(c, nid, eid, byID, byID) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + ep3, errRsp := findEndpoint(c, "firstNet", eid, byName, byID) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + if ep0 != ep1 || ep0 != ep2 || ep0 != ep3 { + t.Fatalf("Diffenrent queries returned different endpoints") + } + + vars = make(map[string]string) + vars[urlNwName] = "" + vars[urlEpName] = "ep1" + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlNwName] = "firstNet" + vars[urlEpName] = "" + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlEpName] = "ep2" + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlEpName] = "firstEp" + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + _, errRsp = findEndpoint(c, "firstNet", "firstEp", byName, byName) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } +} + +func TestJoinLeave(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + nb, err := json.Marshal(networkCreate{Name: "network", NetworkType: bridgeNetType}) + if err != nil { + t.Fatal(err) + } + vars := map[string]string{urlNwName: "network"} + i, errRsp := procCreateNetwork(c, vars, nb) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + nid := i2s(i) + + vars[urlEpName] = "epoint" + eb, err := json.Marshal(endpointCreate{Name: "epoint", NetworkID: nid}) + if err != nil { + t.Fatal(err) + } + _, errRsp = procCreateEndpoint(c, vars, eb) + if errRsp != &createdResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + vbad, err := json.Marshal("bad data") + if err != nil { + t.Fatal(err) + } + _, errRsp = procJoinEndpoint(c, vars, vbad) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + bad, err := json.Marshal(endpointJoin{}) + if err != nil { + t.Fatal(err) + } + _, errRsp = procJoinEndpoint(c, vars, bad) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + cid := "abcdefghi" + jl := endpointJoin{ContainerID: cid} + jlb, err := json.Marshal(jl) + if err != nil { + t.Fatal(err) + } + + vars = make(map[string]string) + vars[urlNwName] = "" + vars[urlEpName] = "" + vars[urlCnID] = cid + _, errRsp = procJoinEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlNwName] = "network1" + vars[urlEpName] = "" + _, errRsp = procJoinEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlNwName] = "network" + vars[urlEpName] = "endpoint" + _, errRsp = procJoinEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlEpName] = "epoint" + delete(vars, urlCnID) + _, errRsp = procJoinEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlCnID] = "who?" + _, errRsp = procJoinEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlCnID] = cid + cdi, errRsp := procJoinEndpoint(c, vars, jlb) + if errRsp != &successResponse { + t.Fatalf("Unexpected failure, got: %v", errRsp) + } + cd := i2c(cdi) + if cd.SandboxKey == "" { + t.Fatalf("Empty sandbox key") + } + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlNwName] = "network2" + _, errRsp = procLeaveEndpoint(c, vars, vbad) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + _, errRsp = procLeaveEndpoint(c, vars, bad) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + vars = make(map[string]string) + vars[urlNwName] = "" + vars[urlEpName] = "" + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + vars[urlNwName] = "network" + vars[urlEpName] = "" + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + vars[urlEpName] = "2epoint" + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + vars[urlEpName] = "epoint" + vars[urlCnID] = "who" + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + delete(vars, urlCnID) + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + vars[urlCnID] = cid + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + _, errRsp = procLeaveEndpoint(c, vars, jlb) + if errRsp == &successResponse { + t.Fatalf("Expected failure, got: %v", errRsp) + } + + _, errRsp = procDeleteEndpoint(c, vars, nil) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } +} + +func TestFindEndpointUtil(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + err = c.ConfigureNetworkDriver(bridgeNetType, nil) + if err != nil { + t.Fatal(err) + } + + nw, err := c.NewNetwork(bridgeNetType, "second", nil) + if err != nil { + t.Fatal(err) + } + nid := nw.ID() + + ep, err := nw.CreateEndpoint("secondEp", nil) + if err != nil { + t.Fatal(err) + } + eid := ep.ID() + + defer checkPanic(t) + findEndpoint(c, nid, "", byID, -1) + + _, errRsp := findEndpoint(c, nid, "", byID, byName) + if errRsp == &successResponse { + t.Fatalf("Expected failure, but got: %v", errRsp) + } + if errRsp.StatusCode != http.StatusBadRequest { + t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode) + } + + ep0, errRsp := findEndpoint(c, nid, "secondEp", byID, byName) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + ep1, errRsp := findEndpoint(c, "second", "secondEp", byName, byName) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + ep2, errRsp := findEndpoint(c, nid, eid, byID, byID) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + ep3, errRsp := findEndpoint(c, "second", eid, byName, byID) + if errRsp != &successResponse { + t.Fatalf("Unexepected failure: %v", errRsp) + } + + if ep0 != ep1 || ep0 != ep2 || ep0 != ep3 { + t.Fatalf("Diffenrent queries returned different endpoints") + } + + ep.Delete() + + _, errRsp = findEndpoint(c, nid, "secondEp", byID, byName) + if errRsp == &successResponse { + t.Fatalf("Expected failure, but got: %v", errRsp) + } + if errRsp.StatusCode != http.StatusNotFound { + t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode) + } + + _, errRsp = findEndpoint(c, "second", "secondEp", byName, byName) + if errRsp == &successResponse { + t.Fatalf("Expected failure, but got: %v", errRsp) + } + if errRsp.StatusCode != http.StatusNotFound { + t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode) + } + + _, errRsp = findEndpoint(c, nid, eid, byID, byID) + if errRsp == &successResponse { + t.Fatalf("Expected failure, but got: %v", errRsp) + } + if errRsp.StatusCode != http.StatusNotFound { + t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode) + } + + _, errRsp = findEndpoint(c, "second", eid, byName, byID) + if errRsp == &successResponse { + t.Fatalf("Expected failure, but got: %v", errRsp) + } + if errRsp.StatusCode != http.StatusNotFound { + t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode) + } +} + +func checkPanic(t *testing.T) { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + } else { + t.Fatalf("Expected to panic, but suceeded") + } +} + +func TestDetectNetworkTargetPanic(t *testing.T) { + defer checkPanic(t) + vars := make(map[string]string) + detectNetworkTarget(vars) +} + +func TestDetectEndpointTargetPanic(t *testing.T) { + defer checkPanic(t) + vars := make(map[string]string) + detectEndpointTarget(vars) +} + +func TestResponseStatus(t *testing.T) { + list := []int{ + http.StatusBadGateway, + http.StatusBadRequest, + http.StatusConflict, + http.StatusContinue, + http.StatusExpectationFailed, + http.StatusForbidden, + http.StatusFound, + http.StatusGatewayTimeout, + http.StatusGone, + http.StatusHTTPVersionNotSupported, + http.StatusInternalServerError, + http.StatusLengthRequired, + http.StatusMethodNotAllowed, + http.StatusMovedPermanently, + http.StatusMultipleChoices, + http.StatusNoContent, + http.StatusNonAuthoritativeInfo, + http.StatusNotAcceptable, + http.StatusNotFound, + http.StatusNotModified, + http.StatusPartialContent, + http.StatusPaymentRequired, + http.StatusPreconditionFailed, + http.StatusProxyAuthRequired, + http.StatusRequestEntityTooLarge, + http.StatusRequestTimeout, + http.StatusRequestURITooLong, + http.StatusRequestedRangeNotSatisfiable, + http.StatusResetContent, + http.StatusServiceUnavailable, + http.StatusSwitchingProtocols, + http.StatusTemporaryRedirect, + http.StatusUnauthorized, + http.StatusUnsupportedMediaType, + http.StatusUseProxy, + } + for _, c := range list { + r := responseStatus{StatusCode: c} + if r.isOK() { + t.Fatalf("isOK() returned true for code% d", c) + } + } + + r := responseStatus{StatusCode: http.StatusOK} + if !r.isOK() { + t.Fatalf("isOK() failed") + } + + r = responseStatus{StatusCode: http.StatusCreated} + if !r.isOK() { + t.Fatalf("isOK() failed") + } +} + +// Local structs for end to end testing of api.go +type localReader struct { + data []byte + beBad bool +} + +func newLocalReader(data []byte) *localReader { + lr := &localReader{data: make([]byte, len(data))} + copy(lr.data, data) + return lr +} + +func (l *localReader) Read(p []byte) (n int, err error) { + if l.beBad { + return 0, errors.New("I am a bad reader") + } + if p == nil { + return -1, fmt.Errorf("nil buffer passed") + } + if l.data == nil || len(l.data) == 0 { + return 0, io.EOF + } + copy(p[:], l.data[:]) + return len(l.data), io.EOF +} + +type localResponseWriter struct { + body []byte + statusCode int +} + +func newWriter() *localResponseWriter { + return &localResponseWriter{} +} + +func (f *localResponseWriter) Header() http.Header { + return make(map[string][]string, 0) +} + +func (f *localResponseWriter) Write(data []byte) (int, error) { + if data == nil { + return -1, fmt.Errorf("nil data passed") + } + + f.body = make([]byte, len(data)) + copy(f.body, data) + + return len(f.body), nil +} + +func (f *localResponseWriter) WriteHeader(c int) { + f.statusCode = c +} + +func TestwriteJSON(t *testing.T) { + testCode := 55 + testData, err := json.Marshal("test data") + if err != nil { + t.Fatal(err) + } + + rsp := newWriter() + writeJSON(rsp, testCode, testData) + if rsp.statusCode != testCode { + t.Fatalf("writeJSON() failed to set the status code. Expected %d. Got %d", testCode, rsp.statusCode) + } + if !bytes.Equal(testData, rsp.body) { + t.Fatalf("writeJSON() failed to set the body. Expected %s. Got %s", testData, rsp.body) + } + +} + +func TestHttpHandlerUninit(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + + h := &httpHandler{c: c} + h.initRouter() + if h.r == nil { + t.Fatalf("initRouter() did not initialize the router") + } + + rsp := newWriter() + req, err := http.NewRequest("GET", "/networks", nil) + if err != nil { + t.Fatal(err) + } + + handleRequest := NewHTTPHandler(nil) + handleRequest(rsp, req) + if rsp.statusCode != http.StatusServiceUnavailable { + t.Fatalf("Expected (%d). Got (%d): %s", http.StatusServiceUnavailable, rsp.statusCode, rsp.body) + } + + handleRequest = NewHTTPHandler(c) + + handleRequest(rsp, req) + if rsp.statusCode != http.StatusOK { + t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body) + } + + n, err := c.NewNetwork(bridgeNetType, "onenet", nil) + if err != nil { + t.Fatal(err) + } + nwr := buildNetworkResource(n) + expected, err := json.Marshal([]networkResource{*nwr}) + if err != nil { + t.Fatal(err) + } + + handleRequest(rsp, req) + if rsp.statusCode != http.StatusOK { + t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body) + } + if len(rsp.body) == 0 { + t.Fatalf("Empty list of networks") + } + if bytes.Equal(rsp.body, expected) { + t.Fatalf("Incorrect list of networks in response's body") + } +} + +func TestHttpHandlerBadBody(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + rsp := newWriter() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + handleRequest := NewHTTPHandler(c) + + req, err := http.NewRequest("POST", "/networks/name/zero-network", &localReader{beBad: true}) + if err != nil { + t.Fatal(err) + } + handleRequest(rsp, req) + if rsp.statusCode != http.StatusBadRequest { + t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusBadRequest, rsp.statusCode, string(rsp.body)) + } + + body := []byte{} + lr := newLocalReader(body) + req, err = http.NewRequest("POST", "/networks/name/zero-network", lr) + if err != nil { + t.Fatal(err) + } + handleRequest(rsp, req) + if rsp.statusCode != http.StatusBadRequest { + t.Fatalf("Unexpected status code. Expected (%d). Got (%d): %s.", http.StatusBadRequest, rsp.statusCode, string(rsp.body)) + } +} + +func TestHttpHandlerGood(t *testing.T) { + defer netutils.SetupTestNetNS(t)() + + rsp := newWriter() + + c, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + handleRequest := NewHTTPHandler(c) + + nc := networkCreate{Name: "zero-network", NetworkType: bridgeNetType} + body, err := json.Marshal(nc) + if err != nil { + t.Fatal(err) + } + + lr := newLocalReader(body) + req, err := http.NewRequest("POST", "/networks/name/zero-network", lr) + if err != nil { + t.Fatal(err) + } + handleRequest(rsp, req) + if rsp.statusCode != http.StatusCreated { + t.Fatalf("Unexpectded status code. Expected (%d). Got (%d): %s.", http.StatusCreated, rsp.statusCode, string(rsp.body)) + } + if len(rsp.body) == 0 { + t.Fatalf("Empty response body") + } + + var id string + err = json.Unmarshal(rsp.body, &id) + if err != nil { + t.Fatal(err) + } + + req, err = http.NewRequest("GET", "/networks/id/"+id, nil) + if err != nil { + t.Fatal(err) + } + handleRequest(rsp, req) + if rsp.statusCode != http.StatusOK { + t.Fatalf("Unexpectded failure: (%d): %s", rsp.statusCode, rsp.body) + } + + var nwr networkResource + err = json.Unmarshal(rsp.body, &nwr) + if err != nil { + t.Fatal(err) + } + if nwr.Name != "zero-network" || id != nwr.ID { + t.Fatalf("Incongruent resource found") + } +}