Browse Source

Merge pull request #14023 from mavenugo/net_ui

experimental network ui
David Calavera 10 years ago
parent
commit
df73d5e0cd

+ 15 - 0
api/client/network.go

@@ -0,0 +1,15 @@
+// +build experimental
+
+package client
+
+import (
+	"os"
+
+	nwclient "github.com/docker/libnetwork/client"
+)
+
+func (cli *DockerCli) CmdNetwork(args ...string) error {
+	nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.call))
+	args = append([]string{"network"}, args...)
+	return nCli.Cmd(os.Args[0], args...)
+}

+ 12 - 0
api/server/server_experimental.go

@@ -0,0 +1,12 @@
+// +build experimental
+
+package server
+
+func (s *Server) registerSubRouter() {
+	httpHandler := s.daemon.NetworkApiRouter()
+
+	subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
+	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
+	subrouter = s.router.PathPrefix("/networks").Subrouter()
+	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
+}

+ 1 - 0
api/server/server_linux.go

@@ -70,6 +70,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
 func (s *Server) AcceptConnections(d *daemon.Daemon) {
 	// Tell the init daemon we are accepting requests
 	s.daemon = d
+	s.registerSubRouter()
 	go systemd.SdNotify("READY=1")
 	// close the lock so the listeners start accepting connections
 	select {

+ 6 - 0
api/server/server_stub.go

@@ -0,0 +1,6 @@
+// +build !experimental
+
+package server
+
+func (s *Server) registerSubRouter() {
+}

+ 1 - 0
api/server/server_windows.go

@@ -45,6 +45,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
 
 func (s *Server) AcceptConnections(d *daemon.Daemon) {
 	s.daemon = d
+	s.registerSubRouter()
 	// close the lock so the listeners start accepting connections
 	select {
 	case <-s.start:

+ 2 - 0
daemon/config.go

@@ -32,6 +32,7 @@ type CommonConfig struct {
 	Pidfile        string
 	Root           string
 	TrustKeyPath   string
+	DefaultNetwork string
 }
 
 // InstallCommonFlags adds command-line options to the top-level flag parser for
@@ -50,6 +51,7 @@ func (config *Config) InstallCommonFlags() {
 	flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU")
 	flag.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header")
 	flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API")
+	flag.StringVar(&config.DefaultNetwork, []string{"-default-network"}, "", "Set default network")
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
 	opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")

+ 33 - 4
daemon/container_linux.go

@@ -737,17 +737,47 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
 	return createOptions, nil
 }
 
+func createDefaultNetwork(controller libnetwork.NetworkController) (libnetwork.Network, error) {
+	createOptions := []libnetwork.NetworkOption{}
+	genericOption := options.Generic{}
+	dnet := controller.Config().Daemon.DefaultNetwork
+	driver := controller.Config().Daemon.DefaultDriver
+
+	// Bridge driver is special due to legacy reasons
+	if runconfig.NetworkMode(driver).IsBridge() {
+		genericOption[netlabel.GenericData] = map[string]interface{}{
+			"BridgeName":            dnet,
+			"AllowNonDefaultBridge": "true",
+		}
+		networkOption := libnetwork.NetworkOptionGeneric(genericOption)
+		createOptions = append(createOptions, networkOption)
+	}
+
+	return controller.NewNetwork(driver, dnet, createOptions...)
+}
+
 func (container *Container) AllocateNetwork() error {
 	mode := container.hostConfig.NetworkMode
+	controller := container.daemon.netController
 	if container.Config.NetworkDisabled || mode.IsContainer() {
 		return nil
 	}
 
+	networkName := mode.NetworkName()
+	if mode.IsDefault() {
+		networkName = controller.Config().Daemon.DefaultNetwork
+	}
+
 	var err error
 
-	n, err := container.daemon.netController.NetworkByName(string(mode))
+	n, err := controller.NetworkByName(networkName)
 	if err != nil {
-		return fmt.Errorf("error locating network with name %s: %v", string(mode), err)
+		if !mode.IsDefault() {
+			return fmt.Errorf("error locating network with name %s: %v", networkName, err)
+		}
+		if n, err = createDefaultNetwork(controller); err != nil {
+			return err
+		}
 	}
 
 	createOptions, err := container.buildCreateEndpointOptions()
@@ -790,9 +820,8 @@ func (container *Container) initializeNetworking() error {
 	// Make sure NetworkMode has an acceptable value before
 	// initializing networking.
 	if container.hostConfig.NetworkMode == runconfig.NetworkMode("") {
-		container.hostConfig.NetworkMode = runconfig.NetworkMode("bridge")
+		container.hostConfig.NetworkMode = runconfig.NetworkMode("default")
 	}
-
 	if container.hostConfig.NetworkMode.IsContainer() {
 		// we need to get the hosts files from the container to join
 		nc, err := container.getNetworkedContainer()

+ 35 - 1
daemon/daemon_unix.go

@@ -5,6 +5,7 @@ package daemon
 import (
 	"fmt"
 	"net"
+	"net/http"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -24,6 +25,8 @@ import (
 	"github.com/docker/docker/volume/local"
 	"github.com/docker/libcontainer/label"
 	"github.com/docker/libnetwork"
+	nwapi "github.com/docker/libnetwork/api"
+	nwconfig "github.com/docker/libnetwork/config"
 	"github.com/docker/libnetwork/netlabel"
 	"github.com/docker/libnetwork/options"
 )
@@ -264,8 +267,35 @@ func isNetworkDisabled(config *Config) bool {
 	return config.Bridge.Iface == disableNetworkBridge
 }
 
+func networkOptions(dconfig *Config) ([]nwconfig.Option, error) {
+	options := []nwconfig.Option{}
+	if dconfig == nil {
+		return options, nil
+	}
+	if strings.TrimSpace(dconfig.DefaultNetwork) != "" {
+		dn := strings.Split(dconfig.DefaultNetwork, ":")
+		if len(dn) < 2 {
+			return nil, fmt.Errorf("default network daemon config must be of the form NETWORKDRIVER:NETWORKNAME")
+		}
+		options = append(options, nwconfig.OptionDefaultDriver(dn[0]))
+		options = append(options, nwconfig.OptionDefaultNetwork(strings.Join(dn[1:], ":")))
+	} else {
+		dd := runconfig.DefaultDaemonNetworkMode()
+		dn := runconfig.DefaultDaemonNetworkMode().NetworkName()
+		options = append(options, nwconfig.OptionDefaultDriver(string(dd)))
+		options = append(options, nwconfig.OptionDefaultNetwork(dn))
+	}
+	options = append(options, nwconfig.OptionLabels(dconfig.Labels))
+	return options, nil
+}
+
 func initNetworkController(config *Config) (libnetwork.NetworkController, error) {
-	controller, err := libnetwork.New()
+	netOptions, err := networkOptions(config)
+	if err != nil {
+		return nil, err
+	}
+
+	controller, err := libnetwork.New(netOptions...)
 	if err != nil {
 		return nil, fmt.Errorf("error obtaining controller instance: %v", err)
 	}
@@ -419,3 +449,7 @@ func setupInitLayer(initLayer string) error {
 	// Layer is ready to use, if it wasn't before.
 	return nil
 }
+
+func (daemon *Daemon) NetworkApiRouter() func(w http.ResponseWriter, req *http.Request) {
+	return nwapi.NewHTTPHandler(daemon.netController)
+}

+ 72 - 0
experimental/networking.md

@@ -0,0 +1,72 @@
+# Experimental: Networking and Services
+
+In this feature:
+
+- `network` become a first class objects in the Docker UI
+
+This is an experimental feature. For information on installing and using experimental features, see [the experimental feature overview](experimental.md).
+
+## Using Networks
+
+        Usage: docker network [OPTIONS] COMMAND [OPTIONS] [arg...]
+
+        Commands:
+            create                   Create a network
+            rm                       Remove a network
+            ls                       List all networks
+            info                     Display information of a network
+
+        Run 'docker network COMMAND --help' for more information on a command.
+
+          --help=false       Print usage
+
+The `docker network` command is used to manage Networks.
+
+To create a network, `docker network create foo`. You can also specify a driver
+if you have loaded a networking plugin e.g `docker network create -d <plugin_name> foo`
+
+        $ docker network create foo
+        aae601f43744bc1f57c515a16c8c7c4989a2cad577978a32e6910b799a6bccf6
+        $ docker network create -d overlay bar
+        d9989793e2f5fe400a58ef77f706d03f668219688ee989ea68ea78b990fa2406
+
+`docker network ls` is used to display the currently configured networks
+
+        $ docker network ls
+        NETWORK ID          NAME                TYPE
+        d367e613ff7f        none                null
+        bd61375b6993        host                host
+        cc455abccfeb        bridge              bridge
+        aae601f43744        foo                 bridge
+        d9989793e2f5        bar                 overlay
+
+To get detailed information on a network, you can use the `docker network info`
+command.
+
+        $ docker network info foo
+        Network Id: aae601f43744bc1f57c515a16c8c7c4989a2cad577978a32e6910b799a6bccf6
+        Name: foo
+        Type: null
+
+If you no longer have need of a network, you can delete it with `docker network rm`
+
+        $ docker network rm bar
+        bar
+        $ docker network ls
+        NETWORK ID          NAME                TYPE
+        aae601f43744        foo                 bridge
+        d367e613ff7f        none                null
+        bd61375b6993        host                host
+        cc455abccfeb        bridge              bridge
+
+
+Currently the only way this network can be used to connect container is via default network-mode.
+Docker daemon supports a configuration flag `--default-network` which takes configuration value of format `NETWORK:DRIVER`, where,
+`NETWORK` is the name of the network created using the `docker network create` command and 
+`DRIVER` represents the in-built drivers such as bridge, overlay, container, host and none. or Remote drivers via Network Plugins.
+When a container is created and if the network mode (`--net`) is not specified, then this default network will be used to connect
+the container. If `--default-network` is not specified, the default network will be the `bridge` driver.
+
+Send us feedback and comments on [#](https://github.com/docker/docker/issues/?),
+or on the usual Google Groups (docker-user, docker-dev) and IRC channels.
+

+ 287 - 0
experimental/networking_api.md

@@ -0,0 +1,287 @@
+# Networking API
+
+### List networks
+
+`GET /networks`
+
+List networks
+
+**Example request**:
+
+        GET /networks HTTP/1.1
+
+**Example response**:
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        [
+          {
+            "name": "none",
+            "id": "8e4e55c6863ef4241c548c1c6fc77289045e9e5d5b5e4875401a675326981898",
+            "type": "null",
+            "endpoints": []
+          },
+          {
+            "name": "host",
+            "id": "062b6d9ea7913fde549e2d186ff0402770658f8c4e769958e1b943ff4e675011",
+            "type": "host",
+            "endpoints": []
+          },
+          {
+            "name": "bridge",
+            "id": "a87dd9a9d58f030962df1c15fb3fa142fbd9261339de458bc89be1895cef2c70",
+            "type": "bridge",
+            "endpoints": []
+          }
+        ]
+
+Query Parameters:
+
+-   **name** – Filter results with the given name
+-   **partial-id** – Filter results using the partial network ID
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Create a Network
+
+`POST /networks`
+
+**Example request**
+
+        POST /networks HTTP/1.1
+        Content-Type: application/json
+
+        {
+          "name": "foo",
+          "network_type": "",
+          "options": {}
+        }
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+        "32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653",
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad request
+-   **500** – server error
+
+### Get a network
+
+`GET /networks/<network_id>`
+
+Get a network
+
+**Example request**:
+
+        GET /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653 HTTP/1.1
+
+**Example response**:
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        {
+          "name": "foo",
+          "id": "32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653",
+          "type": "bridge",
+          "endpoints": []
+        }
+
+Status Codes:
+
+-   **200** – no error
+-   **404** – not found
+-   **500** – server error
+
+### List a networks endpoints
+
+`GET /networks/<network_id>/endpoints`
+
+**Example request**
+
+        GET /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653/endpoints HTTP/1.1
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        [
+            {
+                "id": "7e0c116b882ee489a8a5345a2638c0129099aa47f4ba114edde34e75c1e4ae0d",
+                "name": "/lonely_pasteur",
+                "network": "foo"
+            }
+        ]
+
+Query Parameters:
+
+-   **name** – Filter results with the given name
+-   **partial-id** – Filter results using the partial network ID
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Create an endpoint on a network
+
+`POST /networks/<network_id>/endpoints`
+
+**Example request**
+
+        POST /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653/endpoints HTTP/1.1
+        Content-Type: application/json
+
+        {
+          "name": "baz",
+          "exposed_ports": [
+            {
+              "proto": 6,
+              "port": 8080
+            }
+          ],
+          "port_mapping": null
+        }
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        "b18b795af8bad85cdd691ff24ffa2b08c02219d51992309dd120322689d2ab5a"
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Get an endpoint
+
+`GET /networks/<network_id>/endpoints/<endpoint_id>`
+
+**Example request**
+
+        GET /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653/endpoints/b18b795af8bad85cdd691ff24ffa2b08c02219d51992309dd120322689d2ab5a HTTP/1.1
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        {
+            "id": "b18b795af8bad85cdd691ff24ffa2b08c02219d51992309dd120322689d2ab5a",
+            "name": "baz",
+            "network": "foo"
+        }
+
+Status Codes:
+
+-   **200** – no error
+-   **404** - not found
+-   **500** – server error
+
+### Join an endpoint to a container
+
+`POST /networks/<network_id>/endpoints/<endpoint_id>/containers`
+
+**Example request**
+
+        POST /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653//endpoints/b18b795af8bad85cdd691ff24ffa2b08c02219d51992309dd120322689d2ab5a/containers HTTP/1.1
+        Content-Type: application/json
+
+        {
+            "container_id": "e76f406417031bd24c17aeb9bb2f5968b628b9fb6067da264b234544754bf857",
+            "host_name": null,
+            "domain_name": null,
+            "hosts_path": null,
+            "resolv_conf_path": null,
+            "dns": null,
+            "extra_hosts": null,
+            "parent_updates": null,
+            "use_default_sandbox": true
+        }
+
+**Example response**
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        "/var/run/docker/netns/e76f40641703"
+
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **404** - not found
+-   **500** – server error
+
+### Detach an endpoint from a container
+
+`DELETE /networks/<network_id>/endpoints/<endpoint_id>/containers/<container_id>`
+
+**Example request**
+
+        DELETE /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653/endpoints/b18b795af8bad85cdd691ff24ffa2b08c02219d51992309dd120322689d2ab5a/containers/e76f406417031bd24c17aeb9bb2f5968b628b9fb6067da264b234544754bf857 HTTP/1.1
+        Content-Type: application/json
+
+**Example response**
+
+        HTTP/1.1 200 OK
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **404** - not found
+-   **500** – server error
+
+
+### Delete an endpoint
+
+`DELETE /networks/<network_id>/endpoints/<endpoint_id>`
+
+**Example request**
+
+        DELETE /networks/32fbf63200e2897f5de72cb2a4b653e4b1a523b15116e96e3d73f7849e583653/endpoints/b18b795af8bad85cdd691ff24ffa2b08c02219d51992309dd120322689d2ab5a HTTP/1.1
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+
+Status Codes:
+
+-   **200** – no error
+-   **404** - not found
+-   **500** – server error
+
+### Delete a network
+
+`DELETE /networks/<network_id>`
+
+Delete a network
+
+**Example request**:
+
+        DELETE /networks/0984d158bd8ae108e4d6bc8fcabedf51da9a174b32cc777026d4a29045654951 HTTP/1.1
+
+**Example response**:
+
+        HTTP/1.1 200 OK
+
+Status Codes:
+
+-   **200** – no error
+-   **404** – not found
+-   **500** – server error

+ 1 - 1
hack/vendor.sh

@@ -18,7 +18,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith
 clone hg code.google.com/p/gosqlite 74691fb6f837
 
 #get libnetwork packages
-clone git github.com/docker/libnetwork 3be488927db8d719568917203deddd630a194564
+clone git github.com/docker/libnetwork fc7abaa93fd33a77cc37845adbbc4adf03676dd5
 clone git github.com/docker/libkv e8cde779d58273d240c1eff065352a6cd67027dd
 clone git github.com/vishvananda/netns 5478c060110032f972e86a1f844fdb9a2f008f2c
 clone git github.com/vishvananda/netlink 8eb64238879fed52fd51c5b30ad20b928fb4c36c

+ 1 - 1
integration-cli/docker_api_containers_test.go

@@ -869,7 +869,7 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
 
 	out, err := exec.Command(dockerBinary, "start", "-a", container.Id).CombinedOutput()
 	if err != nil {
-		c.Fatal(out, err)
+		c.Fatal(string(out), err)
 	}
 	if strings.TrimSpace(string(out)) != "/test" {
 		c.Fatalf("expected output `/test`, got %q", out)

+ 72 - 0
integration-cli/docker_api_network_test.go

@@ -0,0 +1,72 @@
+// +build experimental
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/go-check/check"
+)
+
+func isNetworkAvailable(c *check.C, name string) bool {
+	status, body, err := sockRequest("GET", "/networks", nil)
+	c.Assert(status, check.Equals, http.StatusOK)
+	c.Assert(err, check.IsNil)
+
+	var inspectJSON []struct {
+		Name string
+		ID   string
+		Type string
+	}
+	if err = json.Unmarshal(body, &inspectJSON); err != nil {
+		c.Fatalf("unable to unmarshal response body: %v", err)
+	}
+	for _, n := range inspectJSON {
+		if n.Name == name {
+			return true
+		}
+	}
+	return false
+
+}
+
+func (s *DockerSuite) TestNetworkApiGetAll(c *check.C) {
+	defaults := []string{"bridge", "host", "none"}
+	for _, nn := range defaults {
+		if !isNetworkAvailable(c, nn) {
+			c.Fatalf("Missing Default network : %s", nn)
+		}
+	}
+}
+
+func (s *DockerSuite) TestNetworkApiCreateDelete(c *check.C) {
+	name := "testnetwork"
+	config := map[string]interface{}{
+		"name":         name,
+		"network_type": "bridge",
+	}
+
+	status, resp, err := sockRequest("POST", "/networks", config)
+	c.Assert(status, check.Equals, http.StatusCreated)
+	c.Assert(err, check.IsNil)
+
+	if !isNetworkAvailable(c, name) {
+		c.Fatalf("Network %s not found", name)
+	}
+
+	var id string
+	err = json.Unmarshal(resp, &id)
+	if err != nil {
+		c.Fatal(err)
+	}
+
+	status, _, err = sockRequest("DELETE", fmt.Sprintf("/networks/%s", id), nil)
+	c.Assert(status, check.Equals, http.StatusOK)
+	c.Assert(err, check.IsNil)
+
+	if isNetworkAvailable(c, name) {
+		c.Fatalf("Network %s not deleted", name)
+	}
+}

+ 54 - 0
integration-cli/docker_cli_network_test.go

@@ -0,0 +1,54 @@
+// +build experimental
+
+package main
+
+import (
+	"os/exec"
+	"strings"
+
+	"github.com/go-check/check"
+)
+
+func isNetworkPresent(c *check.C, name string) bool {
+	runCmd := exec.Command(dockerBinary, "network", "ls")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil {
+		c.Fatal(out, err)
+	}
+	lines := strings.Split(out, "\n")
+	for i := 1; i < len(lines)-1; i++ {
+		if strings.Contains(lines[i], name) {
+			return true
+		}
+	}
+	return false
+}
+
+func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) {
+	defaults := []string{"bridge", "host", "none"}
+	for _, nn := range defaults {
+		if !isNetworkPresent(c, nn) {
+			c.Fatalf("Missing Default network : %s", nn)
+		}
+	}
+}
+
+func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) {
+	runCmd := exec.Command(dockerBinary, "network", "create", "test")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil {
+		c.Fatal(out, err)
+	}
+	if !isNetworkPresent(c, "test") {
+		c.Fatalf("Network test not found")
+	}
+
+	runCmd = exec.Command(dockerBinary, "network", "rm", "test")
+	out, _, _, err = runCommandWithStdoutStderr(runCmd)
+	if err != nil {
+		c.Fatal(out, err)
+	}
+	if isNetworkPresent(c, "test") {
+		c.Fatalf("Network test is not removed")
+	}
+}

+ 23 - 0
runconfig/hostconfig.go

@@ -21,6 +21,29 @@ func (n NetworkMode) IsPrivate() bool {
 	return !(n.IsHost() || n.IsContainer())
 }
 
+func (n NetworkMode) IsDefault() bool {
+	return n == "default"
+}
+
+func DefaultDaemonNetworkMode() NetworkMode {
+	return NetworkMode("bridge")
+}
+
+func (n NetworkMode) NetworkName() string {
+	if n.IsBridge() {
+		return "bridge"
+	} else if n.IsHost() {
+		return "host"
+	} else if n.IsContainer() {
+		return "container"
+	} else if n.IsNone() {
+		return "none"
+	} else if n.IsDefault() {
+		return "default"
+	}
+	return ""
+}
+
 func (n NetworkMode) IsBridge() bool {
 	return n == "bridge"
 }

+ 2 - 2
runconfig/parse.go

@@ -72,7 +72,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flCpusetMems      = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
 		flCpuQuota        = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota")
 		flBlkioWeight     = cmd.Int64([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
-		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container")
+		flNetMode         = cmd.String([]string{"-net"}, "default", "Set the Network mode for the container")
 		flMacAddress      = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
@@ -485,7 +485,7 @@ func parseKeyValueOpts(opts opts.ListOpts) ([]KeyValuePair, error) {
 func parseNetMode(netMode string) (NetworkMode, error) {
 	parts := strings.Split(netMode, ":")
 	switch mode := parts[0]; mode {
-	case "bridge", "none", "host":
+	case "default", "bridge", "none", "host":
 	case "container":
 		if len(parts) < 2 || parts[1] == "" {
 			return "", fmt.Errorf("invalid container format container:<name|id>")

+ 807 - 0
vendor/src/github.com/docker/libnetwork/api/api.go

@@ -0,0 +1,807 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/docker/libnetwork"
+	"github.com/docker/libnetwork/netlabel"
+	"github.com/docker/libnetwork/types"
+	"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}
+	badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
+)
+
+const (
+	// Resource name regex
+	regex = "[a-zA-Z_0-9-]+"
+	// Router URL variable definition
+	nwName = "{" + urlNwName + ":" + regex + "}"
+	nwID   = "{" + urlNwID + ":" + regex + "}"
+	nwPID  = "{" + urlNwPID + ":" + regex + "}"
+	epName = "{" + urlEpName + ":" + regex + "}"
+	epID   = "{" + urlEpID + ":" + regex + "}"
+	epPID  = "{" + urlEpPID + ":" + regex + "}"
+	cnID   = "{" + urlCnID + ":" + regex + "}"
+
+	// Though this name can be anything, in order to support default network,
+	// we will keep it as name
+	urlNwName = "name"
+	// Internal URL variable name, they can be anything
+	urlNwID   = "network-id"
+	urlNwPID  = "network-partial-id"
+	urlEpName = "endpoint-name"
+	urlEpID   = "endpoint-id"
+	urlEpPID  = "endpoint-partial-id"
+	urlCnID   = "container-id"
+
+	// BridgeNetworkDriver is the built-in default for Network Driver
+	BridgeNetworkDriver = "bridge"
+)
+
+// 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][]struct {
+		url string
+		qrs []string
+		fct processor
+	}{
+		"GET": {
+			// Order matters
+			{"/networks", []string{"name", nwName}, procGetNetworks},
+			{"/networks", []string{"partial-id", nwPID}, procGetNetworks},
+			{"/networks", nil, procGetNetworks},
+			{"/networks/" + nwID, nil, procGetNetwork},
+			{"/networks/" + nwID + "/endpoints", []string{"name", epName}, procGetEndpoints},
+			{"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints},
+			{"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
+			{"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
+			{"/services", []string{"network", nwName}, procGetServices},
+			{"/services", []string{"name", epName}, procGetServices},
+			{"/services", []string{"partial-id", epPID}, procGetServices},
+			{"/services", nil, procGetServices},
+			{"/services/" + epID, nil, procGetService},
+			{"/services/" + epID + "/backend", nil, procGetContainers},
+		},
+		"POST": {
+			{"/networks", nil, procCreateNetwork},
+			{"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
+			{"/networks/" + nwID + "/endpoints/" + epID + "/containers", nil, procJoinEndpoint},
+			{"/services", nil, procPublishService},
+			{"/services/" + epID + "/backend", nil, procAttachBackend},
+		},
+		"DELETE": {
+			{"/networks/" + nwID, nil, procDeleteNetwork},
+			{"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
+			{"/networks/" + nwID + "/endpoints/" + epID + "/containers/" + cnID, nil, procLeaveEndpoint},
+			{"/services/" + epID, nil, procUnpublishService},
+			{"/services/" + epID + "/backend/" + cnID, nil, procDetachBackend},
+		},
+	}
+
+	h.r = mux.NewRouter()
+	for method, routes := range m {
+		for _, route := range routes {
+			r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
+			if route.qrs != nil {
+				r.Queries(route.qrs...)
+			}
+
+			r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
+			if route.qrs != nil {
+				r.Queries(route.qrs...)
+			}
+		}
+	}
+}
+
+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
+			}
+		}
+
+		mvars := mux.Vars(req)
+		rvars := req.URL.Query()
+		// workaround a mux issue which filters out valid queries with empty value
+		for k := range rvars {
+			if _, ok := mvars[k]; !ok {
+				if rvars.Get(k) == "" {
+					mvars[k] = ""
+				}
+			}
+		}
+
+		res, rsp := fct(ctrl, mvars, body)
+		if !rsp.isOK() {
+			http.Error(w, rsp.Status, rsp.StatusCode)
+			return
+		}
+		if res != nil {
+			writeJSON(w, rsp.StatusCode, res)
+		}
+	}
+}
+
+/*****************
+ Resource Builders
+******************/
+
+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()
+	}
+	return r
+}
+
+func buildContainerResource(ci libnetwork.ContainerInfo) *containerResource {
+	r := &containerResource{}
+	if ci != nil {
+		r.ID = ci.ID()
+	}
+	return r
+}
+
+/****************
+ Options Parsers
+*****************/
+
+func (nc *networkCreate) parseOptions() []libnetwork.NetworkOption {
+	var setFctList []libnetwork.NetworkOption
+
+	if nc.Options != nil {
+		setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(nc.Options))
+	}
+
+	return setFctList
+}
+
+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
+*******************/
+
+func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
+	if nc.NetworkType == "" {
+		nc.NetworkType = c.Config().Daemon.DefaultDriver
+	}
+	if nc.NetworkType == BridgeNetworkDriver {
+		if nc.Options == nil {
+			nc.Options = make(map[string]interface{})
+		}
+		genericData, ok := nc.Options[netlabel.GenericData]
+		if !ok {
+			genericData = make(map[string]interface{})
+		}
+		gData := genericData.(map[string]interface{})
+
+		if _, ok := gData["BridgeName"]; !ok {
+			gData["BridgeName"] = nc.Name
+		}
+		if _, ok := gData["AllowNonDefaultBridge"]; !ok {
+			gData["AllowNonDefaultBridge"] = "true"
+		}
+		nc.Options[netlabel.GenericData] = genericData
+	}
+}
+
+/***************************
+ 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}
+	}
+	processCreateDefaults(c, &create)
+
+	nw, err := c.NewNetwork(create.NetworkType, create.Name, create.parseOptions()...)
+	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
+
+	// Look for query filters and validate
+	name, queryByName := vars[urlNwName]
+	shortID, queryByPid := vars[urlNwPID]
+	if queryByName && queryByPid {
+		return nil, &badQueryResponse
+	}
+
+	if queryByName {
+		if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() {
+			list = append(list, buildNetworkResource(nw))
+		}
+	} else if queryByPid {
+		// Return all the prefix-matching networks
+		l := func(nw libnetwork.Network) bool {
+			if strings.HasPrefix(nw.ID(), shortID) {
+				list = append(list, buildNetworkResource(nw))
+			}
+			return false
+		}
+		c.WalkNetworks(l)
+	} else {
+		for _, nw := range c.Networks() {
+			list = append(list, buildNetworkResource(nw))
+		}
+	}
+
+	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}
+	}
+
+	nwT, nwBy := detectNetworkTarget(vars)
+	n, errRsp := findNetwork(c, nwT, nwBy)
+	if !errRsp.isOK() {
+		return "", errRsp
+	}
+
+	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(ec.Name, 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) {
+	// Look for query filters and validate
+	name, queryByName := vars[urlEpName]
+	shortID, queryByPid := vars[urlEpPID]
+	if queryByName && queryByPid {
+		return nil, &badQueryResponse
+	}
+
+	nwT, nwBy := detectNetworkTarget(vars)
+	nw, errRsp := findNetwork(c, nwT, nwBy)
+	if !errRsp.isOK() {
+		return nil, errRsp
+	}
+
+	var list []*endpointResource
+
+	// If query parameter is specified, return a filtered collection
+	if queryByName {
+		if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() {
+			list = append(list, buildEndpointResource(ep))
+		}
+	} else if queryByPid {
+		// Return all the prefix-matching endpoints
+		l := func(ep libnetwork.Endpoint) bool {
+			if strings.HasPrefix(ep.ID(), shortID) {
+				list = append(list, buildEndpointResource(ep))
+			}
+			return false
+		}
+		nw.WalkEndpoints(l)
+	} else {
+		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}
+	}
+
+	nwT, nwBy := detectNetworkTarget(vars)
+	epT, epBy := detectEndpointTarget(vars)
+
+	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
+	if !errRsp.isOK() {
+		return nil, errRsp
+	}
+
+	err = ep.Join(ej.ContainerID, ej.parseOptions()...)
+	if err != nil {
+		return nil, convertNetworkError(err)
+	}
+	return ep.Info().SandboxKey(), &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])
+	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
+}
+
+/******************
+ Service interface
+*******************/
+func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	// Look for query filters and validate
+	nwName, filterByNwName := vars[urlNwName]
+	svName, queryBySvName := vars[urlEpName]
+	shortID, queryBySvPID := vars[urlEpPID]
+
+	if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
+		return nil, &badQueryResponse
+	}
+
+	var list []*endpointResource
+
+	switch {
+	case filterByNwName:
+		// return all service present on the specified network
+		nw, errRsp := findNetwork(c, nwName, byName)
+		if !errRsp.isOK() {
+			return list, &successResponse
+		}
+		for _, ep := range nw.Endpoints() {
+			epr := buildEndpointResource(ep)
+			list = append(list, epr)
+		}
+	case queryBySvName:
+		// Look in each network for the service with the specified name
+		l := func(ep libnetwork.Endpoint) bool {
+			if ep.Name() == svName {
+				list = append(list, buildEndpointResource(ep))
+				return true
+			}
+			return false
+		}
+		for _, nw := range c.Networks() {
+			nw.WalkEndpoints(l)
+		}
+	case queryBySvPID:
+		// Return all the prefix-matching services
+		l := func(ep libnetwork.Endpoint) bool {
+			if strings.HasPrefix(ep.ID(), shortID) {
+				list = append(list, buildEndpointResource(ep))
+			}
+			return false
+		}
+		for _, nw := range c.Networks() {
+			nw.WalkEndpoints(l)
+		}
+	default:
+		for _, nw := range c.Networks() {
+			for _, ep := range nw.Endpoints() {
+				epr := buildEndpointResource(ep)
+				list = append(list, epr)
+			}
+		}
+	}
+
+	return list, &successResponse
+}
+
+func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	epT, epBy := detectEndpointTarget(vars)
+	sv, errRsp := findService(c, epT, epBy)
+	if !errRsp.isOK() {
+		return nil, endpointToService(errRsp)
+	}
+	return buildEndpointResource(sv), &successResponse
+}
+
+func procGetContainers(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	epT, epBy := detectEndpointTarget(vars)
+	sv, errRsp := findService(c, epT, epBy)
+	if !errRsp.isOK() {
+		return nil, endpointToService(errRsp)
+	}
+	var list []*containerResource
+	if sv.ContainerInfo() != nil {
+		list = append(list, buildContainerResource(sv.ContainerInfo()))
+	}
+	return list, &successResponse
+}
+
+func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	var sp servicePublish
+
+	err := json.Unmarshal(body, &sp)
+	if err != nil {
+		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+	}
+
+	n, errRsp := findNetwork(c, sp.Network, byName)
+	if !errRsp.isOK() {
+		return "", errRsp
+	}
+
+	var setFctList []libnetwork.EndpointOption
+	if sp.ExposedPorts != nil {
+		setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts))
+	}
+	if sp.PortMapping != nil {
+		setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping))
+	}
+
+	ep, err := n.CreateEndpoint(sp.Name, setFctList...)
+	if err != nil {
+		return "", endpointToService(convertNetworkError(err))
+	}
+
+	return ep.ID(), &createdResponse
+}
+
+func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	epT, epBy := detectEndpointTarget(vars)
+	sv, errRsp := findService(c, epT, epBy)
+	if !errRsp.isOK() {
+		return nil, errRsp
+	}
+	err := sv.Delete()
+	if err != nil {
+		return nil, endpointToService(convertNetworkError(err))
+	}
+	return nil, &successResponse
+}
+
+func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	var bk endpointJoin
+	err := json.Unmarshal(body, &bk)
+	if err != nil {
+		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
+	}
+
+	epT, epBy := detectEndpointTarget(vars)
+	sv, errRsp := findService(c, epT, epBy)
+	if !errRsp.isOK() {
+		return nil, errRsp
+	}
+
+	err = sv.Join(bk.ContainerID, bk.parseOptions()...)
+	if err != nil {
+		return nil, convertNetworkError(err)
+	}
+	return sv.Info().SandboxKey(), &successResponse
+}
+
+func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
+	epT, epBy := detectEndpointTarget(vars)
+	sv, errRsp := findService(c, epT, epBy)
+	if !errRsp.isOK() {
+		return nil, errRsp
+	}
+
+	err := sv.Leave(vars[urlCnID])
+	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:
+		if s == "" {
+			s = c.Config().Daemon.DefaultNetwork
+		}
+		nw, err = c.NetworkByName(s)
+	default:
+		panic(fmt.Sprintf("unexpected selector for network search: %d", by))
+	}
+	if err != nil {
+		if _, ok := err.(types.NotFoundError); ok {
+			return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
+		}
+		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+	}
+	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 {
+		if _, ok := err.(types.NotFoundError); ok {
+			return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
+		}
+		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
+	}
+	return ep, &successResponse
+}
+
+func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
+	for _, nw := range c.Networks() {
+		var (
+			ep  libnetwork.Endpoint
+			err error
+		)
+		switch svBy {
+		case byID:
+			ep, err = nw.EndpointByID(svs)
+		case byName:
+			ep, err = nw.EndpointByName(svs)
+		default:
+			panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
+		}
+		if err == nil {
+			return ep, &successResponse
+		} else if _, ok := err.(types.NotFoundError); !ok {
+			return nil, convertNetworkError(err)
+		}
+	}
+	return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
+}
+
+func endpointToService(rsp *responseStatus) *responseStatus {
+	rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
+	return rsp
+}
+
+func convertNetworkError(err error) *responseStatus {
+	var code int
+	switch err.(type) {
+	case types.BadRequestError:
+		code = http.StatusBadRequest
+	case types.ForbiddenError:
+		code = http.StatusForbidden
+	case types.NotFoundError:
+		code = http.StatusNotFound
+	case types.TimeoutError:
+		code = http.StatusRequestTimeout
+	case types.NotImplementedError:
+		code = http.StatusNotImplemented
+	case types.NoServiceError:
+		code = http.StatusServiceUnavailable
+	case types.InternalError:
+		code = http.StatusInternalServerError
+	default:
+		code = http.StatusInternalServerError
+	}
+	return &responseStatus{Status: err.Error(), StatusCode: code}
+}
+
+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)
+}

+ 81 - 0
vendor/src/github.com/docker/libnetwork/api/types.go

@@ -0,0 +1,81 @@
+package api
+
+import "github.com/docker/libnetwork/types"
+
+/***********
+ Resources
+************/
+
+// networkResource is the body of the "get network" http response message
+type networkResource struct {
+	Name      string              `json:"name"`
+	ID        string              `json:"id"`
+	Type      string              `json:"type"`
+	Endpoints []*endpointResource `json:"endpoints"`
+}
+
+// endpointResource is the body of the "get endpoint" http response message
+type endpointResource struct {
+	Name    string `json:"name"`
+	ID      string `json:"id"`
+	Network string `json:"network"`
+}
+
+// containerResource is the body of "get service backend" response message
+type containerResource struct {
+	ID string `json:"id"`
+	// will add more fields once labels change is in
+}
+
+/***********
+  Body types
+  ************/
+
+// networkCreate is the expected body of the "create network" http request message
+type networkCreate struct {
+	Name        string                 `json:"name"`
+	NetworkType string                 `json:"network_type"`
+	Options     map[string]interface{} `json:"options"`
+}
+
+// endpointCreate represents the body of the "create endpoint" http request message
+type endpointCreate struct {
+	Name         string                `json:"name"`
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
+}
+
+// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
+type endpointJoin struct {
+	ContainerID       string                 `json:"container_id"`
+	HostName          string                 `json:"host_name"`
+	DomainName        string                 `json:"domain_name"`
+	HostsPath         string                 `json:"hosts_path"`
+	ResolvConfPath    string                 `json:"resolv_conf_path"`
+	DNS               []string               `json:"dns"`
+	ExtraHosts        []endpointExtraHost    `json:"extra_hosts"`
+	ParentUpdates     []endpointParentUpdate `json:"parent_updates"`
+	UseDefaultSandbox bool                   `json:"use_default_sandbox"`
+}
+
+// servicePublish represents the body of the "publish service" http request message
+type servicePublish struct {
+	Name         string                `json:"name"`
+	Network      string                `json:"network_name"`
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
+}
+
+// EndpointExtraHost represents the extra host object
+type endpointExtraHost struct {
+	Name    string `json:"name"`
+	Address string `json:"address"`
+}
+
+// EndpointParentUpdate is the object carrying the information about the
+// endpoint parent that needs to be updated
+type endpointParentUpdate struct {
+	EndpointID string `json:"endpoint_id"`
+	Name       string `json:"name"`
+	Address    string `json:"address"`
+}

+ 115 - 0
vendor/src/github.com/docker/libnetwork/client/client.go

@@ -0,0 +1,115 @@
+package client
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"reflect"
+	"strings"
+
+	flag "github.com/docker/docker/pkg/mflag"
+)
+
+// CallFunc provides environment specific call utility to invoke backend functions from UI
+type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error)
+
+// NetworkCli is the UI object for network subcmds
+type NetworkCli struct {
+	out  io.Writer
+	err  io.Writer
+	call CallFunc
+}
+
+// NewNetworkCli is a convenient function to create a NetworkCli object
+func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
+	return &NetworkCli{
+		out:  out,
+		err:  err,
+		call: call,
+	}
+}
+
+// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
+func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
+	camelArgs := make([]string, len(args))
+	for i, s := range args {
+		if len(s) == 0 {
+			return nil, false
+		}
+		camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
+	}
+	methodName := "Cmd" + strings.Join(camelArgs, "")
+	method := reflect.ValueOf(cli).MethodByName(methodName)
+	if !method.IsValid() {
+		return nil, false
+	}
+	return method.Interface().(func(string, ...string) error), true
+}
+
+// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands.
+// network UI commands are designed to be invoked from multiple parent chains
+func (cli *NetworkCli) Cmd(chain string, args ...string) error {
+	if len(args) > 2 {
+		method, exists := cli.getMethod(args[:3]...)
+		if exists {
+			return method(chain+" "+args[0]+" "+args[1], args[3:]...)
+		}
+	}
+	if len(args) > 1 {
+		method, exists := cli.getMethod(args[:2]...)
+		if exists {
+			return method(chain+" "+args[0], args[2:]...)
+		}
+	}
+	if len(args) > 0 {
+		method, exists := cli.getMethod(args[0])
+		if !exists {
+			return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain)
+		}
+		return method(chain, args[1:]...)
+	}
+	flag.Usage()
+	return nil
+}
+
+// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
+func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
+	var errorHandling flag.ErrorHandling
+	if exitOnError {
+		errorHandling = flag.ExitOnError
+	} else {
+		errorHandling = flag.ContinueOnError
+	}
+	flags := flag.NewFlagSet(name, errorHandling)
+	flags.Usage = func() {
+		flags.ShortUsage()
+		flags.PrintDefaults()
+	}
+	flags.ShortUsage = func() {
+		options := ""
+		if signature != "" {
+			signature = " " + signature
+		}
+		if flags.FlagCountUndeprecated() > 0 {
+			options = " [OPTIONS]"
+		}
+		fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
+		flags.SetOutput(cli.out)
+	}
+	return flags
+}
+
+func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
+	if stream != nil {
+		defer stream.Close()
+	}
+	if err != nil {
+		return nil, statusCode, err
+	}
+	body, err := ioutil.ReadAll(stream)
+	if err != nil {
+		return nil, -1, err
+	}
+	return body, statusCode, nil
+}

+ 231 - 0
vendor/src/github.com/docker/libnetwork/client/network.go

@@ -0,0 +1,231 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/tabwriter"
+
+	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/stringid"
+)
+
+type command struct {
+	name        string
+	description string
+}
+
+var (
+	networkCommands = []command{
+		{"create", "Create a network"},
+		{"rm", "Remove a network"},
+		{"ls", "List all networks"},
+		{"info", "Display information of a network"},
+	}
+)
+
+// CmdNetwork handles the root Network UI
+func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false)
+	cmd.Require(flag.Min, 1)
+	err := cmd.ParseFlags(args, true)
+	if err == nil {
+		cmd.Usage()
+		return fmt.Errorf("invalid command : %v", args)
+	}
+	return err
+}
+
+// CmdNetworkCreate handles Network Create UI
+func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false)
+	flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
+	cmd.Require(flag.Exact, 1)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	// Construct network create request body
+	ops := make(map[string]interface{})
+	nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Options: ops}
+	obj, _, err := readBody(cli.call("POST", "/networks", nc, nil))
+	if err != nil {
+		return err
+	}
+	var replyID string
+	err = json.Unmarshal(obj, &replyID)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(cli.out, "%s\n", replyID)
+	return nil
+}
+
+// CmdNetworkRm handles Network Delete UI
+func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false)
+	cmd.Require(flag.Exact, 1)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+	id, err := lookupNetworkID(cli, cmd.Arg(0))
+	if err != nil {
+		return err
+	}
+	_, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil))
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// CmdNetworkLs handles Network List UI
+func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false)
+	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
+	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
+	nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
+	last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+	obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
+	if err != nil {
+		return err
+	}
+	if *last == -1 && *nLatest {
+		*last = 1
+	}
+
+	var networkResources []networkResource
+	err = json.Unmarshal(obj, &networkResources)
+	if err != nil {
+		return err
+	}
+
+	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+
+	// unless quiet (-q) is specified, print field titles
+	if !*quiet {
+		fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE")
+	}
+
+	for _, networkResource := range networkResources {
+		ID := networkResource.ID
+		netName := networkResource.Name
+		if !*noTrunc {
+			ID = stringid.TruncateID(ID)
+		}
+		if *quiet {
+			fmt.Fprintln(wr, ID)
+			continue
+		}
+		netType := networkResource.Type
+		fmt.Fprintf(wr, "%s\t%s\t%s\t",
+			ID,
+			netName,
+			netType)
+		fmt.Fprint(wr, "\n")
+	}
+	wr.Flush()
+	return nil
+}
+
+// CmdNetworkInfo handles Network Info UI
+func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false)
+	cmd.Require(flag.Exact, 1)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	id, err := lookupNetworkID(cli, cmd.Arg(0))
+	if err != nil {
+		return err
+	}
+
+	obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil))
+	if err != nil {
+		return err
+	}
+	networkResource := &networkResource{}
+	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
+		return err
+	}
+	fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
+	fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
+	fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
+	if networkResource.Services != nil {
+		for _, serviceResource := range networkResource.Services {
+			fmt.Fprintf(cli.out, "  Service Id: %s\n", serviceResource.ID)
+			fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
+		}
+	}
+
+	return nil
+}
+
+// Helper function to predict if a string is a name or id or partial-id
+// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs
+// Being a UI, its most likely that name will be used by the user, which is used to lookup
+// the corresponding ID. If ID is not found, this function will assume that the passed string
+// is an ID by itself.
+
+func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) {
+	obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil))
+	if err != nil {
+		return "", err
+	}
+
+	if statusCode != http.StatusOK {
+		return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+	}
+
+	var list []*networkResource
+	err = json.Unmarshal(obj, &list)
+	if err != nil {
+		return "", err
+	}
+	if len(list) > 0 {
+		// name query filter will always return a single-element collection
+		return list[0].ID, nil
+	}
+
+	// Check for Partial-id
+	obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil))
+	if err != nil {
+		return "", err
+	}
+
+	if statusCode != http.StatusOK {
+		return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
+	}
+
+	err = json.Unmarshal(obj, &list)
+	if err != nil {
+		return "", err
+	}
+	if len(list) == 0 {
+		return "", fmt.Errorf("resource not found %s", nameID)
+	}
+	if len(list) > 1 {
+		return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID)
+	}
+	return list[0].ID, nil
+}
+
+func networkUsage(chain string) string {
+	help := "Commands:\n"
+
+	for _, cmd := range networkCommands {
+		help += fmt.Sprintf("    %-25.25s%s\n", cmd.name, cmd.description)
+	}
+
+	help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
+	return help
+}

+ 362 - 0
vendor/src/github.com/docker/libnetwork/client/service.go

@@ -0,0 +1,362 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+	"text/tabwriter"
+
+	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/stringid"
+)
+
+var (
+	serviceCommands = []command{
+		{"publish", "Publish a service"},
+		{"unpublish", "Remove a service"},
+		{"attach", "Attach a backend (container) to the service"},
+		{"detach", "Detach the backend from the service"},
+		{"ls", "Lists all services"},
+		{"info", "Display information about a service"},
+	}
+)
+
+func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
+	// Sanity Check
+	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
+	if err != nil {
+		return "", err
+	}
+	var nwList []networkResource
+	if err = json.Unmarshal(obj, &nwList); err != nil {
+		return "", err
+	}
+	if len(nwList) == 0 {
+		return "", fmt.Errorf("Network %s does not exist", nwName)
+	}
+
+	if nwName == "" {
+		obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
+		if err != nil {
+			return "", err
+		}
+		networkResource := &networkResource{}
+		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
+			return "", err
+		}
+		nwName = networkResource.Name
+	}
+
+	// Query service by name
+	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
+	if err != nil {
+		return "", err
+	}
+
+	if statusCode != http.StatusOK {
+		return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
+	}
+
+	var list []*serviceResource
+	if err = json.Unmarshal(obj, &list); err != nil {
+		return "", err
+	}
+	for _, sr := range list {
+		if sr.Network == nwName {
+			return sr.ID, nil
+		}
+	}
+
+	// Query service by Partial-id (this covers full id as well)
+	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
+	if err != nil {
+		return "", err
+	}
+
+	if statusCode != http.StatusOK {
+		return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
+	}
+
+	if err = json.Unmarshal(obj, &list); err != nil {
+		return "", err
+	}
+	for _, sr := range list {
+		if sr.Network == nwName {
+			return sr.ID, nil
+		}
+	}
+
+	return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
+}
+
+func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
+	// Container is a Docker resource, ask docker about it.
+	// In case of connecton error, we assume we are running in dnet and return whatever was passed to us
+	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
+	if err != nil {
+		// We are probably running outside of docker
+		return cnNameID, nil
+	}
+
+	var x map[string]interface{}
+	err = json.Unmarshal(obj, &x)
+	if err != nil {
+		return "", err
+	}
+	if iid, ok := x["Id"]; ok {
+		if id, ok := iid.(string); ok {
+			return id, nil
+		}
+		return "", fmt.Errorf("Unexpected data type for container ID in json response")
+	}
+	return "", fmt.Errorf("Cannot find container ID in json response")
+}
+
+// CmdService handles the service UI
+func (cli *NetworkCli) CmdService(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
+	cmd.Require(flag.Min, 1)
+	err := cmd.ParseFlags(args, true)
+	if err == nil {
+		cmd.Usage()
+		return fmt.Errorf("Invalid command : %v", args)
+	}
+	return err
+}
+
+// Parse service name for "SERVICE[.NETWORK]" format
+func parseServiceName(name string) (string, string) {
+	s := strings.Split(name, ".")
+	var sName, nName string
+	if len(s) > 1 {
+		nName = s[len(s)-1]
+		sName = strings.Join(s[:len(s)-1], ".")
+	} else {
+		sName = s[0]
+	}
+	return sName, nName
+}
+
+// CmdServicePublish handles service create UI
+func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
+	cmd.Require(flag.Exact, 1)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	sn, nn := parseServiceName(cmd.Arg(0))
+	sc := serviceCreate{Name: sn, Network: nn}
+	obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
+	if err != nil {
+		return err
+	}
+
+	var replyID string
+	err = json.Unmarshal(obj, &replyID)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(cli.out, "%s\n", replyID)
+	return nil
+}
+
+// CmdServiceUnpublish handles service delete UI
+func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
+	cmd.Require(flag.Exact, 1)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	sn, nn := parseServiceName(cmd.Arg(0))
+	serviceID, err := lookupServiceID(cli, nn, sn)
+	if err != nil {
+		return err
+	}
+
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, nil, nil))
+
+	return err
+}
+
+// CmdServiceLs handles service list UI
+func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
+	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
+	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
+
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	var obj []byte
+	if *flNetwork == "" {
+		obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
+	} else {
+		obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
+	}
+	if err != nil {
+		return err
+	}
+
+	var serviceResources []serviceResource
+	err = json.Unmarshal(obj, &serviceResources)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+	// unless quiet (-q) is specified, print field titles
+	if !*quiet {
+		fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER")
+	}
+
+	for _, sr := range serviceResources {
+		ID := sr.ID
+		bkID, err := getBackendID(cli, ID)
+		if err != nil {
+			return err
+		}
+		if !*noTrunc {
+			ID = stringid.TruncateID(ID)
+			bkID = stringid.TruncateID(bkID)
+		}
+		if !*quiet {
+			fmt.Fprintf(wr, "%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID)
+		} else {
+			fmt.Fprintln(wr, ID)
+		}
+	}
+	wr.Flush()
+
+	return nil
+}
+
+func getBackendID(cli *NetworkCli, servID string) (string, error) {
+	var (
+		obj []byte
+		err error
+		bk  string
+	)
+
+	if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
+		var bkl []backendResource
+		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&bkl); err == nil {
+			if len(bkl) > 0 {
+				bk = bkl[0].ID
+			}
+		} else {
+			// Only print a message, don't make the caller cli fail for this
+			fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)", servID, err)
+		}
+	}
+
+	return bk, err
+}
+
+// CmdServiceInfo handles service info UI
+func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false)
+	cmd.Require(flag.Min, 1)
+
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	sn, nn := parseServiceName(cmd.Arg(0))
+	serviceID, err := lookupServiceID(cli, nn, sn)
+	if err != nil {
+		return err
+	}
+
+	obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
+	if err != nil {
+		return err
+	}
+
+	sr := &serviceResource{}
+	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
+		return err
+	}
+
+	fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
+	fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
+	fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
+
+	return nil
+}
+
+// CmdServiceAttach handles service attach UI
+func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
+	cmd.Require(flag.Min, 2)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
+	if err != nil {
+		return err
+	}
+
+	sn, nn := parseServiceName(cmd.Arg(1))
+	serviceID, err := lookupServiceID(cli, nn, sn)
+	if err != nil {
+		return err
+	}
+
+	nc := serviceAttach{ContainerID: containerID}
+
+	_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
+
+	return err
+}
+
+// CmdServiceDetach handles service detach UI
+func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
+	cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
+	cmd.Require(flag.Min, 2)
+	err := cmd.ParseFlags(args, true)
+	if err != nil {
+		return err
+	}
+
+	sn, nn := parseServiceName(cmd.Arg(1))
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
+	if err != nil {
+		return err
+	}
+
+	serviceID, err := lookupServiceID(cli, nn, sn)
+	if err != nil {
+		return err
+	}
+
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+containerID, nil, nil))
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func serviceUsage(chain string) string {
+	help := "Commands:\n"
+
+	for _, cmd := range serviceCommands {
+		help += fmt.Sprintf("    %-10.10s%s\n", cmd.name, cmd.description)
+	}
+
+	help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain)
+	return help
+}

+ 73 - 0
vendor/src/github.com/docker/libnetwork/client/types.go

@@ -0,0 +1,73 @@
+package client
+
+import "github.com/docker/libnetwork/types"
+
+/***********
+ Resources
+************/
+
+// networkResource is the body of the "get network" http response message
+type networkResource struct {
+	Name     string             `json:"name"`
+	ID       string             `json:"id"`
+	Type     string             `json:"type"`
+	Services []*serviceResource `json:"services"`
+}
+
+// serviceResource is the body of the "get service" http response message
+type serviceResource struct {
+	Name    string `json:"name"`
+	ID      string `json:"id"`
+	Network string `json:"network"`
+}
+
+// backendResource is the body of "get service backend" response message
+type backendResource struct {
+	ID string `json:"id"`
+}
+
+/***********
+  Body types
+  ************/
+
+// networkCreate is the expected body of the "create network" http request message
+type networkCreate struct {
+	Name        string                 `json:"name"`
+	NetworkType string                 `json:"network_type"`
+	Options     map[string]interface{} `json:"options"`
+}
+
+// serviceCreate represents the body of the "publish service" http request message
+type serviceCreate struct {
+	Name         string                `json:"name"`
+	Network      string                `json:"network_name"`
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
+}
+
+// serviceAttach represents the expected body of the "attach/detach backend to/from service" http request messages
+type serviceAttach struct {
+	ContainerID       string                `json:"container_id"`
+	HostName          string                `json:"host_name"`
+	DomainName        string                `json:"domain_name"`
+	HostsPath         string                `json:"hosts_path"`
+	ResolvConfPath    string                `json:"resolv_conf_path"`
+	DNS               []string              `json:"dns"`
+	ExtraHosts        []serviceExtraHost    `json:"extra_hosts"`
+	ParentUpdates     []serviceParentUpdate `json:"parent_updates"`
+	UseDefaultSandbox bool                  `json:"use_default_sandbox"`
+}
+
+// serviceExtraHost represents the extra host object
+type serviceExtraHost struct {
+	Name    string `json:"name"`
+	Address string `json:"address"`
+}
+
+// EndpointParentUpdate is the object carrying the information about the
+// endpoint parent that needs to be updated
+type serviceParentUpdate struct {
+	EndpointID string `json:"service_id"`
+	Name       string `json:"name"`
+	Address    string `json:"address"`
+}

+ 21 - 0
vendor/src/github.com/docker/libnetwork/config/config.go

@@ -4,6 +4,7 @@ import (
 	"strings"
 
 	"github.com/BurntSushi/toml"
+	"github.com/docker/libnetwork/netlabel"
 )
 
 // Config encapsulates configurations of various Libnetwork components
@@ -18,6 +19,7 @@ type DaemonCfg struct {
 	Debug          bool
 	DefaultNetwork string
 	DefaultDriver  string
+	Labels         []string
 }
 
 // ClusterCfg represents cluster configuration
@@ -66,6 +68,17 @@ func OptionDefaultDriver(dd string) Option {
 	}
 }
 
+// OptionLabels function returns an option setter for labels
+func OptionLabels(labels []string) Option {
+	return func(c *Config) {
+		for _, label := range labels {
+			if strings.HasPrefix(label, netlabel.Prefix) {
+				c.Daemon.Labels = append(c.Daemon.Labels, label)
+			}
+		}
+	}
+}
+
 // OptionKVProvider function returns an option setter for kvstore provider
 func OptionKVProvider(provider string) Option {
 	return func(c *Config) {
@@ -88,3 +101,11 @@ func (c *Config) ProcessOptions(options ...Option) {
 		}
 	}
 }
+
+// IsValidName validates configuration objects supported by libnetwork
+func IsValidName(name string) bool {
+	if name == "" || strings.Contains(name, ".") {
+		return false
+	}
+	return true
+}

+ 7 - 1
vendor/src/github.com/docker/libnetwork/controller.go

@@ -169,6 +169,9 @@ func (c *controller) hostLeaveCallback(hosts []net.IP) {
 func (c *controller) Config() config.Config {
 	c.Lock()
 	defer c.Unlock()
+	if c.cfg == nil {
+		return config.Config{}
+	}
 	return *c.cfg
 }
 
@@ -185,6 +188,9 @@ func (c *controller) ConfigureNetworkDriver(networkType string, options map[stri
 func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error {
 	c.Lock()
 	defer c.Unlock()
+	if !config.IsValidName(networkType) {
+		return ErrInvalidName(networkType)
+	}
 	if _, ok := c.drivers[networkType]; ok {
 		return driverapi.ErrActiveRegistration(networkType)
 	}
@@ -195,7 +201,7 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver,
 // NewNetwork creates a new network of the specified network type. The options
 // are network specific and modeled in a generic way.
 func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {
-	if name == "" {
+	if !config.IsValidName(name) {
 		return nil, ErrInvalidName(name)
 	}
 	// Check if a network already exists with the specified network name

+ 11 - 5
vendor/src/github.com/docker/libnetwork/netlabel/labels.go

@@ -1,18 +1,24 @@
 package netlabel
 
 const (
+	// Prefix constant marks the reserved label space for libnetwork
+	Prefix = "com.docker.network"
+
+	// DriverPrefix constant marks the reserved label space for libnetwork drivers
+	DriverPrefix = Prefix + ".driver"
+
 	// GenericData constant that helps to identify an option as a Generic constant
-	GenericData = "io.docker.network.generic"
+	GenericData = Prefix + ".generic"
 
 	// PortMap constant represents Port Mapping
-	PortMap = "io.docker.network.endpoint.portmap"
+	PortMap = Prefix + ".portmap"
 
 	// MacAddress constant represents Mac Address config of a Container
-	MacAddress = "io.docker.network.endpoint.macaddress"
+	MacAddress = Prefix + ".endpoint.macaddress"
 
 	// ExposedPorts constant represents exposedports of a Container
-	ExposedPorts = "io.docker.network.endpoint.exposedports"
+	ExposedPorts = Prefix + ".endpoint.exposedports"
 
 	//EnableIPv6 constant represents enabling IPV6 at network level
-	EnableIPv6 = "io.docker.network.enable_ipv6"
+	EnableIPv6 = Prefix + ".enable_ipv6"
 )

+ 2 - 1
vendor/src/github.com/docker/libnetwork/network.go

@@ -6,6 +6,7 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/libnetwork/config"
 	"github.com/docker/libnetwork/datastore"
 	"github.com/docker/libnetwork/driverapi"
 	"github.com/docker/libnetwork/netlabel"
@@ -274,7 +275,7 @@ func (n *network) addEndpoint(ep *endpoint) error {
 
 func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
 	var err error
-	if name == "" {
+	if !config.IsValidName(name) {
 		return nil, ErrInvalidName(name)
 	}