Browse code

register libnetwork API and UI with docker parent chain

This commit also brings in the ability to specify a default network and its
corresponding driver as daemon flags. This helps in existing clients to
make use of newer networking features provided by libnetwork.

Signed-off-by: Madhu Venugopal <madhu@docker.com>

Madhu Venugopal authored on 2015/05/20 21:20:19
Showing 24 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+// +build experimental
1
+
2
+package client
3
+
4
+import (
5
+	"os"
6
+
7
+	nwclient "github.com/docker/libnetwork/client"
8
+)
9
+
10
+func (cli *DockerCli) CmdNetwork(args ...string) error {
11
+	nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.call))
12
+	args = append([]string{"network"}, args...)
13
+	return nCli.Cmd(os.Args[0], args...)
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+// +build experimental
1
+
2
+package server
3
+
4
+func (s *Server) registerSubRouter() {
5
+	httpHandler := s.daemon.NetworkApiRouter()
6
+
7
+	subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
8
+	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
9
+	subrouter = s.router.PathPrefix("/networks").Subrouter()
10
+	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
11
+}
... ...
@@ -70,6 +70,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
70 70
 func (s *Server) AcceptConnections(d *daemon.Daemon) {
71 71
 	// Tell the init daemon we are accepting requests
72 72
 	s.daemon = d
73
+	s.registerSubRouter()
73 74
 	go systemd.SdNotify("READY=1")
74 75
 	// close the lock so the listeners start accepting connections
75 76
 	select {
76 77
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+// +build !experimental
1
+
2
+package server
3
+
4
+func (s *Server) registerSubRouter() {
5
+}
... ...
@@ -45,6 +45,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
45 45
 
46 46
 func (s *Server) AcceptConnections(d *daemon.Daemon) {
47 47
 	s.daemon = d
48
+	s.registerSubRouter()
48 49
 	// close the lock so the listeners start accepting connections
49 50
 	select {
50 51
 	case <-s.start:
... ...
@@ -32,6 +32,7 @@ type CommonConfig struct {
32 32
 	Pidfile        string
33 33
 	Root           string
34 34
 	TrustKeyPath   string
35
+	DefaultNetwork string
35 36
 }
36 37
 
37 38
 // InstallCommonFlags adds command-line options to the top-level flag parser for
... ...
@@ -50,6 +51,7 @@ func (config *Config) InstallCommonFlags() {
50 50
 	flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU")
51 51
 	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")
52 52
 	flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API")
53
+	flag.StringVar(&config.DefaultNetwork, []string{"-default-network"}, "", "Set default network")
53 54
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
54 55
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
55 56
 	opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
... ...
@@ -737,17 +737,47 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
737 737
 	return createOptions, nil
738 738
 }
739 739
 
740
+func createDefaultNetwork(controller libnetwork.NetworkController) (libnetwork.Network, error) {
741
+	createOptions := []libnetwork.NetworkOption{}
742
+	genericOption := options.Generic{}
743
+	dnet := controller.Config().Daemon.DefaultNetwork
744
+	driver := controller.Config().Daemon.DefaultDriver
745
+
746
+	// Bridge driver is special due to legacy reasons
747
+	if runconfig.NetworkMode(driver).IsBridge() {
748
+		genericOption[netlabel.GenericData] = map[string]interface{}{
749
+			"BridgeName":            dnet,
750
+			"AllowNonDefaultBridge": "true",
751
+		}
752
+		networkOption := libnetwork.NetworkOptionGeneric(genericOption)
753
+		createOptions = append(createOptions, networkOption)
754
+	}
755
+
756
+	return controller.NewNetwork(driver, dnet, createOptions...)
757
+}
758
+
740 759
 func (container *Container) AllocateNetwork() error {
741 760
 	mode := container.hostConfig.NetworkMode
761
+	controller := container.daemon.netController
742 762
 	if container.Config.NetworkDisabled || mode.IsContainer() {
743 763
 		return nil
744 764
 	}
745 765
 
766
+	networkName := mode.NetworkName()
767
+	if mode.IsDefault() {
768
+		networkName = controller.Config().Daemon.DefaultNetwork
769
+	}
770
+
746 771
 	var err error
747 772
 
748
-	n, err := container.daemon.netController.NetworkByName(string(mode))
773
+	n, err := controller.NetworkByName(networkName)
749 774
 	if err != nil {
750
-		return fmt.Errorf("error locating network with name %s: %v", string(mode), err)
775
+		if !mode.IsDefault() {
776
+			return fmt.Errorf("error locating network with name %s: %v", networkName, err)
777
+		}
778
+		if n, err = createDefaultNetwork(controller); err != nil {
779
+			return err
780
+		}
751 781
 	}
752 782
 
753 783
 	createOptions, err := container.buildCreateEndpointOptions()
... ...
@@ -790,9 +820,8 @@ func (container *Container) initializeNetworking() error {
790 790
 	// Make sure NetworkMode has an acceptable value before
791 791
 	// initializing networking.
792 792
 	if container.hostConfig.NetworkMode == runconfig.NetworkMode("") {
793
-		container.hostConfig.NetworkMode = runconfig.NetworkMode("bridge")
793
+		container.hostConfig.NetworkMode = runconfig.NetworkMode("default")
794 794
 	}
795
-
796 795
 	if container.hostConfig.NetworkMode.IsContainer() {
797 796
 		// we need to get the hosts files from the container to join
798 797
 		nc, err := container.getNetworkedContainer()
... ...
@@ -5,6 +5,7 @@ package daemon
5 5
 import (
6 6
 	"fmt"
7 7
 	"net"
8
+	"net/http"
8 9
 	"os"
9 10
 	"path/filepath"
10 11
 	"runtime"
... ...
@@ -24,6 +25,8 @@ import (
24 24
 	"github.com/docker/docker/volume/local"
25 25
 	"github.com/docker/libcontainer/label"
26 26
 	"github.com/docker/libnetwork"
27
+	nwapi "github.com/docker/libnetwork/api"
28
+	nwconfig "github.com/docker/libnetwork/config"
27 29
 	"github.com/docker/libnetwork/netlabel"
28 30
 	"github.com/docker/libnetwork/options"
29 31
 )
... ...
@@ -264,8 +267,35 @@ func isNetworkDisabled(config *Config) bool {
264 264
 	return config.Bridge.Iface == disableNetworkBridge
265 265
 }
266 266
 
267
+func networkOptions(dconfig *Config) ([]nwconfig.Option, error) {
268
+	options := []nwconfig.Option{}
269
+	if dconfig == nil {
270
+		return options, nil
271
+	}
272
+	if strings.TrimSpace(dconfig.DefaultNetwork) != "" {
273
+		dn := strings.Split(dconfig.DefaultNetwork, ":")
274
+		if len(dn) < 2 {
275
+			return nil, fmt.Errorf("default network daemon config must be of the form NETWORKDRIVER:NETWORKNAME")
276
+		}
277
+		options = append(options, nwconfig.OptionDefaultDriver(dn[0]))
278
+		options = append(options, nwconfig.OptionDefaultNetwork(strings.Join(dn[1:], ":")))
279
+	} else {
280
+		dd := runconfig.DefaultDaemonNetworkMode()
281
+		dn := runconfig.DefaultDaemonNetworkMode().NetworkName()
282
+		options = append(options, nwconfig.OptionDefaultDriver(string(dd)))
283
+		options = append(options, nwconfig.OptionDefaultNetwork(dn))
284
+	}
285
+	options = append(options, nwconfig.OptionLabels(dconfig.Labels))
286
+	return options, nil
287
+}
288
+
267 289
 func initNetworkController(config *Config) (libnetwork.NetworkController, error) {
268
-	controller, err := libnetwork.New()
290
+	netOptions, err := networkOptions(config)
291
+	if err != nil {
292
+		return nil, err
293
+	}
294
+
295
+	controller, err := libnetwork.New(netOptions...)
269 296
 	if err != nil {
270 297
 		return nil, fmt.Errorf("error obtaining controller instance: %v", err)
271 298
 	}
... ...
@@ -419,3 +449,7 @@ func setupInitLayer(initLayer string) error {
419 419
 	// Layer is ready to use, if it wasn't before.
420 420
 	return nil
421 421
 }
422
+
423
+func (daemon *Daemon) NetworkApiRouter() func(w http.ResponseWriter, req *http.Request) {
424
+	return nwapi.NewHTTPHandler(daemon.netController)
425
+}
... ...
@@ -18,7 +18,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith
18 18
 clone hg code.google.com/p/gosqlite 74691fb6f837
19 19
 
20 20
 #get libnetwork packages
21
-clone git github.com/docker/libnetwork 3be488927db8d719568917203deddd630a194564
21
+clone git github.com/docker/libnetwork fc7abaa93fd33a77cc37845adbbc4adf03676dd5
22 22
 clone git github.com/docker/libkv e8cde779d58273d240c1eff065352a6cd67027dd
23 23
 clone git github.com/vishvananda/netns 5478c060110032f972e86a1f844fdb9a2f008f2c
24 24
 clone git github.com/vishvananda/netlink 8eb64238879fed52fd51c5b30ad20b928fb4c36c
... ...
@@ -869,7 +869,7 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
869 869
 
870 870
 	out, err := exec.Command(dockerBinary, "start", "-a", container.Id).CombinedOutput()
871 871
 	if err != nil {
872
-		c.Fatal(out, err)
872
+		c.Fatal(string(out), err)
873 873
 	}
874 874
 	if strings.TrimSpace(string(out)) != "/test" {
875 875
 		c.Fatalf("expected output `/test`, got %q", out)
876 876
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+// +build experimental
1
+
2
+package main
3
+
4
+import (
5
+	"encoding/json"
6
+	"fmt"
7
+	"net/http"
8
+
9
+	"github.com/go-check/check"
10
+)
11
+
12
+func isNetworkAvailable(c *check.C, name string) bool {
13
+	status, body, err := sockRequest("GET", "/networks", nil)
14
+	c.Assert(status, check.Equals, http.StatusOK)
15
+	c.Assert(err, check.IsNil)
16
+
17
+	var inspectJSON []struct {
18
+		Name string
19
+		ID   string
20
+		Type string
21
+	}
22
+	if err = json.Unmarshal(body, &inspectJSON); err != nil {
23
+		c.Fatalf("unable to unmarshal response body: %v", err)
24
+	}
25
+	for _, n := range inspectJSON {
26
+		if n.Name == name {
27
+			return true
28
+		}
29
+	}
30
+	return false
31
+
32
+}
33
+
34
+func (s *DockerSuite) TestNetworkApiGetAll(c *check.C) {
35
+	defaults := []string{"bridge", "host", "none"}
36
+	for _, nn := range defaults {
37
+		if !isNetworkAvailable(c, nn) {
38
+			c.Fatalf("Missing Default network : %s", nn)
39
+		}
40
+	}
41
+}
42
+
43
+func (s *DockerSuite) TestNetworkApiCreateDelete(c *check.C) {
44
+	name := "testnetwork"
45
+	config := map[string]interface{}{
46
+		"name":         name,
47
+		"network_type": "bridge",
48
+	}
49
+
50
+	status, resp, err := sockRequest("POST", "/networks", config)
51
+	c.Assert(status, check.Equals, http.StatusCreated)
52
+	c.Assert(err, check.IsNil)
53
+
54
+	if !isNetworkAvailable(c, name) {
55
+		c.Fatalf("Network %s not found", name)
56
+	}
57
+
58
+	var id string
59
+	err = json.Unmarshal(resp, &id)
60
+	if err != nil {
61
+		c.Fatal(err)
62
+	}
63
+
64
+	status, _, err = sockRequest("DELETE", fmt.Sprintf("/networks/%s", id), nil)
65
+	c.Assert(status, check.Equals, http.StatusOK)
66
+	c.Assert(err, check.IsNil)
67
+
68
+	if isNetworkAvailable(c, name) {
69
+		c.Fatalf("Network %s not deleted", name)
70
+	}
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+// +build experimental
1
+
2
+package main
3
+
4
+import (
5
+	"os/exec"
6
+	"strings"
7
+
8
+	"github.com/go-check/check"
9
+)
10
+
11
+func isNetworkPresent(c *check.C, name string) bool {
12
+	runCmd := exec.Command(dockerBinary, "network", "ls")
13
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
14
+	if err != nil {
15
+		c.Fatal(out, err)
16
+	}
17
+	lines := strings.Split(out, "\n")
18
+	for i := 1; i < len(lines)-1; i++ {
19
+		if strings.Contains(lines[i], name) {
20
+			return true
21
+		}
22
+	}
23
+	return false
24
+}
25
+
26
+func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) {
27
+	defaults := []string{"bridge", "host", "none"}
28
+	for _, nn := range defaults {
29
+		if !isNetworkPresent(c, nn) {
30
+			c.Fatalf("Missing Default network : %s", nn)
31
+		}
32
+	}
33
+}
34
+
35
+func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) {
36
+	runCmd := exec.Command(dockerBinary, "network", "create", "test")
37
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
38
+	if err != nil {
39
+		c.Fatal(out, err)
40
+	}
41
+	if !isNetworkPresent(c, "test") {
42
+		c.Fatalf("Network test not found")
43
+	}
44
+
45
+	runCmd = exec.Command(dockerBinary, "network", "rm", "test")
46
+	out, _, _, err = runCommandWithStdoutStderr(runCmd)
47
+	if err != nil {
48
+		c.Fatal(out, err)
49
+	}
50
+	if isNetworkPresent(c, "test") {
51
+		c.Fatalf("Network test is not removed")
52
+	}
53
+}
... ...
@@ -21,6 +21,29 @@ func (n NetworkMode) IsPrivate() bool {
21 21
 	return !(n.IsHost() || n.IsContainer())
22 22
 }
23 23
 
24
+func (n NetworkMode) IsDefault() bool {
25
+	return n == "default"
26
+}
27
+
28
+func DefaultDaemonNetworkMode() NetworkMode {
29
+	return NetworkMode("bridge")
30
+}
31
+
32
+func (n NetworkMode) NetworkName() string {
33
+	if n.IsBridge() {
34
+		return "bridge"
35
+	} else if n.IsHost() {
36
+		return "host"
37
+	} else if n.IsContainer() {
38
+		return "container"
39
+	} else if n.IsNone() {
40
+		return "none"
41
+	} else if n.IsDefault() {
42
+		return "default"
43
+	}
44
+	return ""
45
+}
46
+
24 47
 func (n NetworkMode) IsBridge() bool {
25 48
 	return n == "bridge"
26 49
 }
... ...
@@ -72,7 +72,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
72 72
 		flCpusetMems      = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
73 73
 		flCpuQuota        = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota")
74 74
 		flBlkioWeight     = cmd.Int64([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
75
-		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container")
75
+		flNetMode         = cmd.String([]string{"-net"}, "default", "Set the Network mode for the container")
76 76
 		flMacAddress      = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
77 77
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
78 78
 		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) {
485 485
 func parseNetMode(netMode string) (NetworkMode, error) {
486 486
 	parts := strings.Split(netMode, ":")
487 487
 	switch mode := parts[0]; mode {
488
-	case "bridge", "none", "host":
488
+	case "default", "bridge", "none", "host":
489 489
 	case "container":
490 490
 		if len(parts) < 2 || parts[1] == "" {
491 491
 			return "", fmt.Errorf("invalid container format container:<name|id>")
492 492
new file mode 100644
... ...
@@ -0,0 +1,807 @@
0
+package api
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"net/http"
7
+	"strings"
8
+
9
+	"github.com/docker/libnetwork"
10
+	"github.com/docker/libnetwork/netlabel"
11
+	"github.com/docker/libnetwork/types"
12
+	"github.com/gorilla/mux"
13
+)
14
+
15
+var (
16
+	successResponse  = responseStatus{Status: "Success", StatusCode: http.StatusOK}
17
+	createdResponse  = responseStatus{Status: "Created", StatusCode: http.StatusCreated}
18
+	mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest}
19
+	badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
20
+)
21
+
22
+const (
23
+	// Resource name regex
24
+	regex = "[a-zA-Z_0-9-]+"
25
+	// Router URL variable definition
26
+	nwName = "{" + urlNwName + ":" + regex + "}"
27
+	nwID   = "{" + urlNwID + ":" + regex + "}"
28
+	nwPID  = "{" + urlNwPID + ":" + regex + "}"
29
+	epName = "{" + urlEpName + ":" + regex + "}"
30
+	epID   = "{" + urlEpID + ":" + regex + "}"
31
+	epPID  = "{" + urlEpPID + ":" + regex + "}"
32
+	cnID   = "{" + urlCnID + ":" + regex + "}"
33
+
34
+	// Though this name can be anything, in order to support default network,
35
+	// we will keep it as name
36
+	urlNwName = "name"
37
+	// Internal URL variable name, they can be anything
38
+	urlNwID   = "network-id"
39
+	urlNwPID  = "network-partial-id"
40
+	urlEpName = "endpoint-name"
41
+	urlEpID   = "endpoint-id"
42
+	urlEpPID  = "endpoint-partial-id"
43
+	urlCnID   = "container-id"
44
+
45
+	// BridgeNetworkDriver is the built-in default for Network Driver
46
+	BridgeNetworkDriver = "bridge"
47
+)
48
+
49
+// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork
50
+func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) {
51
+	h := &httpHandler{c: c}
52
+	h.initRouter()
53
+	return h.handleRequest
54
+}
55
+
56
+type responseStatus struct {
57
+	Status     string
58
+	StatusCode int
59
+}
60
+
61
+func (r *responseStatus) isOK() bool {
62
+	return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated
63
+}
64
+
65
+type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus)
66
+
67
+type httpHandler struct {
68
+	c libnetwork.NetworkController
69
+	r *mux.Router
70
+}
71
+
72
+func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) {
73
+	// Make sure the service is there
74
+	if h.c == nil {
75
+		http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable)
76
+		return
77
+	}
78
+
79
+	// Get handler from router and execute it
80
+	h.r.ServeHTTP(w, req)
81
+}
82
+
83
+func (h *httpHandler) initRouter() {
84
+	m := map[string][]struct {
85
+		url string
86
+		qrs []string
87
+		fct processor
88
+	}{
89
+		"GET": {
90
+			// Order matters
91
+			{"/networks", []string{"name", nwName}, procGetNetworks},
92
+			{"/networks", []string{"partial-id", nwPID}, procGetNetworks},
93
+			{"/networks", nil, procGetNetworks},
94
+			{"/networks/" + nwID, nil, procGetNetwork},
95
+			{"/networks/" + nwID + "/endpoints", []string{"name", epName}, procGetEndpoints},
96
+			{"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints},
97
+			{"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
98
+			{"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
99
+			{"/services", []string{"network", nwName}, procGetServices},
100
+			{"/services", []string{"name", epName}, procGetServices},
101
+			{"/services", []string{"partial-id", epPID}, procGetServices},
102
+			{"/services", nil, procGetServices},
103
+			{"/services/" + epID, nil, procGetService},
104
+			{"/services/" + epID + "/backend", nil, procGetContainers},
105
+		},
106
+		"POST": {
107
+			{"/networks", nil, procCreateNetwork},
108
+			{"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
109
+			{"/networks/" + nwID + "/endpoints/" + epID + "/containers", nil, procJoinEndpoint},
110
+			{"/services", nil, procPublishService},
111
+			{"/services/" + epID + "/backend", nil, procAttachBackend},
112
+		},
113
+		"DELETE": {
114
+			{"/networks/" + nwID, nil, procDeleteNetwork},
115
+			{"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
116
+			{"/networks/" + nwID + "/endpoints/" + epID + "/containers/" + cnID, nil, procLeaveEndpoint},
117
+			{"/services/" + epID, nil, procUnpublishService},
118
+			{"/services/" + epID + "/backend/" + cnID, nil, procDetachBackend},
119
+		},
120
+	}
121
+
122
+	h.r = mux.NewRouter()
123
+	for method, routes := range m {
124
+		for _, route := range routes {
125
+			r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
126
+			if route.qrs != nil {
127
+				r.Queries(route.qrs...)
128
+			}
129
+
130
+			r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
131
+			if route.qrs != nil {
132
+				r.Queries(route.qrs...)
133
+			}
134
+		}
135
+	}
136
+}
137
+
138
+func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc {
139
+	return func(w http.ResponseWriter, req *http.Request) {
140
+		var (
141
+			body []byte
142
+			err  error
143
+		)
144
+		if req.Body != nil {
145
+			body, err = ioutil.ReadAll(req.Body)
146
+			if err != nil {
147
+				http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest)
148
+				return
149
+			}
150
+		}
151
+
152
+		mvars := mux.Vars(req)
153
+		rvars := req.URL.Query()
154
+		// workaround a mux issue which filters out valid queries with empty value
155
+		for k := range rvars {
156
+			if _, ok := mvars[k]; !ok {
157
+				if rvars.Get(k) == "" {
158
+					mvars[k] = ""
159
+				}
160
+			}
161
+		}
162
+
163
+		res, rsp := fct(ctrl, mvars, body)
164
+		if !rsp.isOK() {
165
+			http.Error(w, rsp.Status, rsp.StatusCode)
166
+			return
167
+		}
168
+		if res != nil {
169
+			writeJSON(w, rsp.StatusCode, res)
170
+		}
171
+	}
172
+}
173
+
174
+/*****************
175
+ Resource Builders
176
+******************/
177
+
178
+func buildNetworkResource(nw libnetwork.Network) *networkResource {
179
+	r := &networkResource{}
180
+	if nw != nil {
181
+		r.Name = nw.Name()
182
+		r.ID = nw.ID()
183
+		r.Type = nw.Type()
184
+		epl := nw.Endpoints()
185
+		r.Endpoints = make([]*endpointResource, 0, len(epl))
186
+		for _, e := range epl {
187
+			epr := buildEndpointResource(e)
188
+			r.Endpoints = append(r.Endpoints, epr)
189
+		}
190
+	}
191
+	return r
192
+}
193
+
194
+func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
195
+	r := &endpointResource{}
196
+	if ep != nil {
197
+		r.Name = ep.Name()
198
+		r.ID = ep.ID()
199
+		r.Network = ep.Network()
200
+	}
201
+	return r
202
+}
203
+
204
+func buildContainerResource(ci libnetwork.ContainerInfo) *containerResource {
205
+	r := &containerResource{}
206
+	if ci != nil {
207
+		r.ID = ci.ID()
208
+	}
209
+	return r
210
+}
211
+
212
+/****************
213
+ Options Parsers
214
+*****************/
215
+
216
+func (nc *networkCreate) parseOptions() []libnetwork.NetworkOption {
217
+	var setFctList []libnetwork.NetworkOption
218
+
219
+	if nc.Options != nil {
220
+		setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(nc.Options))
221
+	}
222
+
223
+	return setFctList
224
+}
225
+
226
+func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption {
227
+	var setFctList []libnetwork.EndpointOption
228
+	if ej.HostName != "" {
229
+		setFctList = append(setFctList, libnetwork.JoinOptionHostname(ej.HostName))
230
+	}
231
+	if ej.DomainName != "" {
232
+		setFctList = append(setFctList, libnetwork.JoinOptionDomainname(ej.DomainName))
233
+	}
234
+	if ej.HostsPath != "" {
235
+		setFctList = append(setFctList, libnetwork.JoinOptionHostsPath(ej.HostsPath))
236
+	}
237
+	if ej.ResolvConfPath != "" {
238
+		setFctList = append(setFctList, libnetwork.JoinOptionResolvConfPath(ej.ResolvConfPath))
239
+	}
240
+	if ej.UseDefaultSandbox {
241
+		setFctList = append(setFctList, libnetwork.JoinOptionUseDefaultSandbox())
242
+	}
243
+	if ej.DNS != nil {
244
+		for _, d := range ej.DNS {
245
+			setFctList = append(setFctList, libnetwork.JoinOptionDNS(d))
246
+		}
247
+	}
248
+	if ej.ExtraHosts != nil {
249
+		for _, e := range ej.ExtraHosts {
250
+			setFctList = append(setFctList, libnetwork.JoinOptionExtraHost(e.Name, e.Address))
251
+		}
252
+	}
253
+	if ej.ParentUpdates != nil {
254
+		for _, p := range ej.ParentUpdates {
255
+			setFctList = append(setFctList, libnetwork.JoinOptionParentUpdate(p.EndpointID, p.Name, p.Address))
256
+		}
257
+	}
258
+	return setFctList
259
+}
260
+
261
+/******************
262
+ Process functions
263
+*******************/
264
+
265
+func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
266
+	if nc.NetworkType == "" {
267
+		nc.NetworkType = c.Config().Daemon.DefaultDriver
268
+	}
269
+	if nc.NetworkType == BridgeNetworkDriver {
270
+		if nc.Options == nil {
271
+			nc.Options = make(map[string]interface{})
272
+		}
273
+		genericData, ok := nc.Options[netlabel.GenericData]
274
+		if !ok {
275
+			genericData = make(map[string]interface{})
276
+		}
277
+		gData := genericData.(map[string]interface{})
278
+
279
+		if _, ok := gData["BridgeName"]; !ok {
280
+			gData["BridgeName"] = nc.Name
281
+		}
282
+		if _, ok := gData["AllowNonDefaultBridge"]; !ok {
283
+			gData["AllowNonDefaultBridge"] = "true"
284
+		}
285
+		nc.Options[netlabel.GenericData] = genericData
286
+	}
287
+}
288
+
289
+/***************************
290
+ NetworkController interface
291
+****************************/
292
+func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
293
+	var create networkCreate
294
+
295
+	err := json.Unmarshal(body, &create)
296
+	if err != nil {
297
+		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
298
+	}
299
+	processCreateDefaults(c, &create)
300
+
301
+	nw, err := c.NewNetwork(create.NetworkType, create.Name, create.parseOptions()...)
302
+	if err != nil {
303
+		return "", convertNetworkError(err)
304
+	}
305
+
306
+	return nw.ID(), &createdResponse
307
+}
308
+
309
+func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
310
+	t, by := detectNetworkTarget(vars)
311
+	nw, errRsp := findNetwork(c, t, by)
312
+	if !errRsp.isOK() {
313
+		return nil, errRsp
314
+	}
315
+	return buildNetworkResource(nw), &successResponse
316
+}
317
+
318
+func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
319
+	var list []*networkResource
320
+
321
+	// Look for query filters and validate
322
+	name, queryByName := vars[urlNwName]
323
+	shortID, queryByPid := vars[urlNwPID]
324
+	if queryByName && queryByPid {
325
+		return nil, &badQueryResponse
326
+	}
327
+
328
+	if queryByName {
329
+		if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() {
330
+			list = append(list, buildNetworkResource(nw))
331
+		}
332
+	} else if queryByPid {
333
+		// Return all the prefix-matching networks
334
+		l := func(nw libnetwork.Network) bool {
335
+			if strings.HasPrefix(nw.ID(), shortID) {
336
+				list = append(list, buildNetworkResource(nw))
337
+			}
338
+			return false
339
+		}
340
+		c.WalkNetworks(l)
341
+	} else {
342
+		for _, nw := range c.Networks() {
343
+			list = append(list, buildNetworkResource(nw))
344
+		}
345
+	}
346
+
347
+	return list, &successResponse
348
+}
349
+
350
+/******************
351
+ Network interface
352
+*******************/
353
+func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
354
+	var ec endpointCreate
355
+
356
+	err := json.Unmarshal(body, &ec)
357
+	if err != nil {
358
+		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
359
+	}
360
+
361
+	nwT, nwBy := detectNetworkTarget(vars)
362
+	n, errRsp := findNetwork(c, nwT, nwBy)
363
+	if !errRsp.isOK() {
364
+		return "", errRsp
365
+	}
366
+
367
+	var setFctList []libnetwork.EndpointOption
368
+	if ec.ExposedPorts != nil {
369
+		setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts))
370
+	}
371
+	if ec.PortMapping != nil {
372
+		setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping))
373
+	}
374
+
375
+	ep, err := n.CreateEndpoint(ec.Name, setFctList...)
376
+	if err != nil {
377
+		return "", convertNetworkError(err)
378
+	}
379
+
380
+	return ep.ID(), &createdResponse
381
+}
382
+
383
+func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
384
+	nwT, nwBy := detectNetworkTarget(vars)
385
+	epT, epBy := detectEndpointTarget(vars)
386
+
387
+	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
388
+	if !errRsp.isOK() {
389
+		return nil, errRsp
390
+	}
391
+
392
+	return buildEndpointResource(ep), &successResponse
393
+}
394
+
395
+func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
396
+	// Look for query filters and validate
397
+	name, queryByName := vars[urlEpName]
398
+	shortID, queryByPid := vars[urlEpPID]
399
+	if queryByName && queryByPid {
400
+		return nil, &badQueryResponse
401
+	}
402
+
403
+	nwT, nwBy := detectNetworkTarget(vars)
404
+	nw, errRsp := findNetwork(c, nwT, nwBy)
405
+	if !errRsp.isOK() {
406
+		return nil, errRsp
407
+	}
408
+
409
+	var list []*endpointResource
410
+
411
+	// If query parameter is specified, return a filtered collection
412
+	if queryByName {
413
+		if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() {
414
+			list = append(list, buildEndpointResource(ep))
415
+		}
416
+	} else if queryByPid {
417
+		// Return all the prefix-matching endpoints
418
+		l := func(ep libnetwork.Endpoint) bool {
419
+			if strings.HasPrefix(ep.ID(), shortID) {
420
+				list = append(list, buildEndpointResource(ep))
421
+			}
422
+			return false
423
+		}
424
+		nw.WalkEndpoints(l)
425
+	} else {
426
+		for _, ep := range nw.Endpoints() {
427
+			epr := buildEndpointResource(ep)
428
+			list = append(list, epr)
429
+		}
430
+	}
431
+
432
+	return list, &successResponse
433
+}
434
+
435
+func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
436
+	target, by := detectNetworkTarget(vars)
437
+
438
+	nw, errRsp := findNetwork(c, target, by)
439
+	if !errRsp.isOK() {
440
+		return nil, errRsp
441
+	}
442
+
443
+	err := nw.Delete()
444
+	if err != nil {
445
+		return nil, convertNetworkError(err)
446
+	}
447
+
448
+	return nil, &successResponse
449
+}
450
+
451
+/******************
452
+ Endpoint interface
453
+*******************/
454
+func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
455
+	var ej endpointJoin
456
+	err := json.Unmarshal(body, &ej)
457
+	if err != nil {
458
+		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
459
+	}
460
+
461
+	nwT, nwBy := detectNetworkTarget(vars)
462
+	epT, epBy := detectEndpointTarget(vars)
463
+
464
+	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
465
+	if !errRsp.isOK() {
466
+		return nil, errRsp
467
+	}
468
+
469
+	err = ep.Join(ej.ContainerID, ej.parseOptions()...)
470
+	if err != nil {
471
+		return nil, convertNetworkError(err)
472
+	}
473
+	return ep.Info().SandboxKey(), &successResponse
474
+}
475
+
476
+func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
477
+	nwT, nwBy := detectNetworkTarget(vars)
478
+	epT, epBy := detectEndpointTarget(vars)
479
+
480
+	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
481
+	if !errRsp.isOK() {
482
+		return nil, errRsp
483
+	}
484
+
485
+	err := ep.Leave(vars[urlCnID])
486
+	if err != nil {
487
+		return nil, convertNetworkError(err)
488
+	}
489
+
490
+	return nil, &successResponse
491
+}
492
+
493
+func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
494
+	nwT, nwBy := detectNetworkTarget(vars)
495
+	epT, epBy := detectEndpointTarget(vars)
496
+
497
+	ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
498
+	if !errRsp.isOK() {
499
+		return nil, errRsp
500
+	}
501
+
502
+	err := ep.Delete()
503
+	if err != nil {
504
+		return nil, convertNetworkError(err)
505
+	}
506
+
507
+	return nil, &successResponse
508
+}
509
+
510
+/******************
511
+ Service interface
512
+*******************/
513
+func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
514
+	// Look for query filters and validate
515
+	nwName, filterByNwName := vars[urlNwName]
516
+	svName, queryBySvName := vars[urlEpName]
517
+	shortID, queryBySvPID := vars[urlEpPID]
518
+
519
+	if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
520
+		return nil, &badQueryResponse
521
+	}
522
+
523
+	var list []*endpointResource
524
+
525
+	switch {
526
+	case filterByNwName:
527
+		// return all service present on the specified network
528
+		nw, errRsp := findNetwork(c, nwName, byName)
529
+		if !errRsp.isOK() {
530
+			return list, &successResponse
531
+		}
532
+		for _, ep := range nw.Endpoints() {
533
+			epr := buildEndpointResource(ep)
534
+			list = append(list, epr)
535
+		}
536
+	case queryBySvName:
537
+		// Look in each network for the service with the specified name
538
+		l := func(ep libnetwork.Endpoint) bool {
539
+			if ep.Name() == svName {
540
+				list = append(list, buildEndpointResource(ep))
541
+				return true
542
+			}
543
+			return false
544
+		}
545
+		for _, nw := range c.Networks() {
546
+			nw.WalkEndpoints(l)
547
+		}
548
+	case queryBySvPID:
549
+		// Return all the prefix-matching services
550
+		l := func(ep libnetwork.Endpoint) bool {
551
+			if strings.HasPrefix(ep.ID(), shortID) {
552
+				list = append(list, buildEndpointResource(ep))
553
+			}
554
+			return false
555
+		}
556
+		for _, nw := range c.Networks() {
557
+			nw.WalkEndpoints(l)
558
+		}
559
+	default:
560
+		for _, nw := range c.Networks() {
561
+			for _, ep := range nw.Endpoints() {
562
+				epr := buildEndpointResource(ep)
563
+				list = append(list, epr)
564
+			}
565
+		}
566
+	}
567
+
568
+	return list, &successResponse
569
+}
570
+
571
+func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
572
+	epT, epBy := detectEndpointTarget(vars)
573
+	sv, errRsp := findService(c, epT, epBy)
574
+	if !errRsp.isOK() {
575
+		return nil, endpointToService(errRsp)
576
+	}
577
+	return buildEndpointResource(sv), &successResponse
578
+}
579
+
580
+func procGetContainers(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
581
+	epT, epBy := detectEndpointTarget(vars)
582
+	sv, errRsp := findService(c, epT, epBy)
583
+	if !errRsp.isOK() {
584
+		return nil, endpointToService(errRsp)
585
+	}
586
+	var list []*containerResource
587
+	if sv.ContainerInfo() != nil {
588
+		list = append(list, buildContainerResource(sv.ContainerInfo()))
589
+	}
590
+	return list, &successResponse
591
+}
592
+
593
+func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
594
+	var sp servicePublish
595
+
596
+	err := json.Unmarshal(body, &sp)
597
+	if err != nil {
598
+		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
599
+	}
600
+
601
+	n, errRsp := findNetwork(c, sp.Network, byName)
602
+	if !errRsp.isOK() {
603
+		return "", errRsp
604
+	}
605
+
606
+	var setFctList []libnetwork.EndpointOption
607
+	if sp.ExposedPorts != nil {
608
+		setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts))
609
+	}
610
+	if sp.PortMapping != nil {
611
+		setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping))
612
+	}
613
+
614
+	ep, err := n.CreateEndpoint(sp.Name, setFctList...)
615
+	if err != nil {
616
+		return "", endpointToService(convertNetworkError(err))
617
+	}
618
+
619
+	return ep.ID(), &createdResponse
620
+}
621
+
622
+func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
623
+	epT, epBy := detectEndpointTarget(vars)
624
+	sv, errRsp := findService(c, epT, epBy)
625
+	if !errRsp.isOK() {
626
+		return nil, errRsp
627
+	}
628
+	err := sv.Delete()
629
+	if err != nil {
630
+		return nil, endpointToService(convertNetworkError(err))
631
+	}
632
+	return nil, &successResponse
633
+}
634
+
635
+func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
636
+	var bk endpointJoin
637
+	err := json.Unmarshal(body, &bk)
638
+	if err != nil {
639
+		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
640
+	}
641
+
642
+	epT, epBy := detectEndpointTarget(vars)
643
+	sv, errRsp := findService(c, epT, epBy)
644
+	if !errRsp.isOK() {
645
+		return nil, errRsp
646
+	}
647
+
648
+	err = sv.Join(bk.ContainerID, bk.parseOptions()...)
649
+	if err != nil {
650
+		return nil, convertNetworkError(err)
651
+	}
652
+	return sv.Info().SandboxKey(), &successResponse
653
+}
654
+
655
+func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
656
+	epT, epBy := detectEndpointTarget(vars)
657
+	sv, errRsp := findService(c, epT, epBy)
658
+	if !errRsp.isOK() {
659
+		return nil, errRsp
660
+	}
661
+
662
+	err := sv.Leave(vars[urlCnID])
663
+	if err != nil {
664
+		return nil, convertNetworkError(err)
665
+	}
666
+
667
+	return nil, &successResponse
668
+}
669
+
670
+/***********
671
+  Utilities
672
+************/
673
+const (
674
+	byID = iota
675
+	byName
676
+)
677
+
678
+func detectNetworkTarget(vars map[string]string) (string, int) {
679
+	if target, ok := vars[urlNwName]; ok {
680
+		return target, byName
681
+	}
682
+	if target, ok := vars[urlNwID]; ok {
683
+		return target, byID
684
+	}
685
+	// vars are populated from the URL, following cannot happen
686
+	panic("Missing URL variable parameter for network")
687
+}
688
+
689
+func detectEndpointTarget(vars map[string]string) (string, int) {
690
+	if target, ok := vars[urlEpName]; ok {
691
+		return target, byName
692
+	}
693
+	if target, ok := vars[urlEpID]; ok {
694
+		return target, byID
695
+	}
696
+	// vars are populated from the URL, following cannot happen
697
+	panic("Missing URL variable parameter for endpoint")
698
+}
699
+
700
+func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) {
701
+	var (
702
+		nw  libnetwork.Network
703
+		err error
704
+	)
705
+	switch by {
706
+	case byID:
707
+		nw, err = c.NetworkByID(s)
708
+	case byName:
709
+		if s == "" {
710
+			s = c.Config().Daemon.DefaultNetwork
711
+		}
712
+		nw, err = c.NetworkByName(s)
713
+	default:
714
+		panic(fmt.Sprintf("unexpected selector for network search: %d", by))
715
+	}
716
+	if err != nil {
717
+		if _, ok := err.(types.NotFoundError); ok {
718
+			return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
719
+		}
720
+		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
721
+	}
722
+	return nw, &successResponse
723
+}
724
+
725
+func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) {
726
+	nw, errRsp := findNetwork(c, ns, nwBy)
727
+	if !errRsp.isOK() {
728
+		return nil, errRsp
729
+	}
730
+	var (
731
+		err error
732
+		ep  libnetwork.Endpoint
733
+	)
734
+	switch epBy {
735
+	case byID:
736
+		ep, err = nw.EndpointByID(es)
737
+	case byName:
738
+		ep, err = nw.EndpointByName(es)
739
+	default:
740
+		panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy))
741
+	}
742
+	if err != nil {
743
+		if _, ok := err.(types.NotFoundError); ok {
744
+			return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
745
+		}
746
+		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
747
+	}
748
+	return ep, &successResponse
749
+}
750
+
751
+func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
752
+	for _, nw := range c.Networks() {
753
+		var (
754
+			ep  libnetwork.Endpoint
755
+			err error
756
+		)
757
+		switch svBy {
758
+		case byID:
759
+			ep, err = nw.EndpointByID(svs)
760
+		case byName:
761
+			ep, err = nw.EndpointByName(svs)
762
+		default:
763
+			panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
764
+		}
765
+		if err == nil {
766
+			return ep, &successResponse
767
+		} else if _, ok := err.(types.NotFoundError); !ok {
768
+			return nil, convertNetworkError(err)
769
+		}
770
+	}
771
+	return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
772
+}
773
+
774
+func endpointToService(rsp *responseStatus) *responseStatus {
775
+	rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
776
+	return rsp
777
+}
778
+
779
+func convertNetworkError(err error) *responseStatus {
780
+	var code int
781
+	switch err.(type) {
782
+	case types.BadRequestError:
783
+		code = http.StatusBadRequest
784
+	case types.ForbiddenError:
785
+		code = http.StatusForbidden
786
+	case types.NotFoundError:
787
+		code = http.StatusNotFound
788
+	case types.TimeoutError:
789
+		code = http.StatusRequestTimeout
790
+	case types.NotImplementedError:
791
+		code = http.StatusNotImplemented
792
+	case types.NoServiceError:
793
+		code = http.StatusServiceUnavailable
794
+	case types.InternalError:
795
+		code = http.StatusInternalServerError
796
+	default:
797
+		code = http.StatusInternalServerError
798
+	}
799
+	return &responseStatus{Status: err.Error(), StatusCode: code}
800
+}
801
+
802
+func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
803
+	w.Header().Set("Content-Type", "application/json")
804
+	w.WriteHeader(code)
805
+	return json.NewEncoder(w).Encode(v)
806
+}
0 807
new file mode 100644
... ...
@@ -0,0 +1,81 @@
0
+package api
1
+
2
+import "github.com/docker/libnetwork/types"
3
+
4
+/***********
5
+ Resources
6
+************/
7
+
8
+// networkResource is the body of the "get network" http response message
9
+type networkResource struct {
10
+	Name      string              `json:"name"`
11
+	ID        string              `json:"id"`
12
+	Type      string              `json:"type"`
13
+	Endpoints []*endpointResource `json:"endpoints"`
14
+}
15
+
16
+// endpointResource is the body of the "get endpoint" http response message
17
+type endpointResource struct {
18
+	Name    string `json:"name"`
19
+	ID      string `json:"id"`
20
+	Network string `json:"network"`
21
+}
22
+
23
+// containerResource is the body of "get service backend" response message
24
+type containerResource struct {
25
+	ID string `json:"id"`
26
+	// will add more fields once labels change is in
27
+}
28
+
29
+/***********
30
+  Body types
31
+  ************/
32
+
33
+// networkCreate is the expected body of the "create network" http request message
34
+type networkCreate struct {
35
+	Name        string                 `json:"name"`
36
+	NetworkType string                 `json:"network_type"`
37
+	Options     map[string]interface{} `json:"options"`
38
+}
39
+
40
+// endpointCreate represents the body of the "create endpoint" http request message
41
+type endpointCreate struct {
42
+	Name         string                `json:"name"`
43
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
44
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
45
+}
46
+
47
+// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
48
+type endpointJoin struct {
49
+	ContainerID       string                 `json:"container_id"`
50
+	HostName          string                 `json:"host_name"`
51
+	DomainName        string                 `json:"domain_name"`
52
+	HostsPath         string                 `json:"hosts_path"`
53
+	ResolvConfPath    string                 `json:"resolv_conf_path"`
54
+	DNS               []string               `json:"dns"`
55
+	ExtraHosts        []endpointExtraHost    `json:"extra_hosts"`
56
+	ParentUpdates     []endpointParentUpdate `json:"parent_updates"`
57
+	UseDefaultSandbox bool                   `json:"use_default_sandbox"`
58
+}
59
+
60
+// servicePublish represents the body of the "publish service" http request message
61
+type servicePublish struct {
62
+	Name         string                `json:"name"`
63
+	Network      string                `json:"network_name"`
64
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
65
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
66
+}
67
+
68
+// EndpointExtraHost represents the extra host object
69
+type endpointExtraHost struct {
70
+	Name    string `json:"name"`
71
+	Address string `json:"address"`
72
+}
73
+
74
+// EndpointParentUpdate is the object carrying the information about the
75
+// endpoint parent that needs to be updated
76
+type endpointParentUpdate struct {
77
+	EndpointID string `json:"endpoint_id"`
78
+	Name       string `json:"name"`
79
+	Address    string `json:"address"`
80
+}
0 81
new file mode 100644
... ...
@@ -0,0 +1,115 @@
0
+package client
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"io/ioutil"
6
+	"net/http"
7
+	"reflect"
8
+	"strings"
9
+
10
+	flag "github.com/docker/docker/pkg/mflag"
11
+)
12
+
13
+// CallFunc provides environment specific call utility to invoke backend functions from UI
14
+type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error)
15
+
16
+// NetworkCli is the UI object for network subcmds
17
+type NetworkCli struct {
18
+	out  io.Writer
19
+	err  io.Writer
20
+	call CallFunc
21
+}
22
+
23
+// NewNetworkCli is a convenient function to create a NetworkCli object
24
+func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
25
+	return &NetworkCli{
26
+		out:  out,
27
+		err:  err,
28
+		call: call,
29
+	}
30
+}
31
+
32
+// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
33
+func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
34
+	camelArgs := make([]string, len(args))
35
+	for i, s := range args {
36
+		if len(s) == 0 {
37
+			return nil, false
38
+		}
39
+		camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
40
+	}
41
+	methodName := "Cmd" + strings.Join(camelArgs, "")
42
+	method := reflect.ValueOf(cli).MethodByName(methodName)
43
+	if !method.IsValid() {
44
+		return nil, false
45
+	}
46
+	return method.Interface().(func(string, ...string) error), true
47
+}
48
+
49
+// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands.
50
+// network UI commands are designed to be invoked from multiple parent chains
51
+func (cli *NetworkCli) Cmd(chain string, args ...string) error {
52
+	if len(args) > 2 {
53
+		method, exists := cli.getMethod(args[:3]...)
54
+		if exists {
55
+			return method(chain+" "+args[0]+" "+args[1], args[3:]...)
56
+		}
57
+	}
58
+	if len(args) > 1 {
59
+		method, exists := cli.getMethod(args[:2]...)
60
+		if exists {
61
+			return method(chain+" "+args[0], args[2:]...)
62
+		}
63
+	}
64
+	if len(args) > 0 {
65
+		method, exists := cli.getMethod(args[0])
66
+		if !exists {
67
+			return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain)
68
+		}
69
+		return method(chain, args[1:]...)
70
+	}
71
+	flag.Usage()
72
+	return nil
73
+}
74
+
75
+// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
76
+func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
77
+	var errorHandling flag.ErrorHandling
78
+	if exitOnError {
79
+		errorHandling = flag.ExitOnError
80
+	} else {
81
+		errorHandling = flag.ContinueOnError
82
+	}
83
+	flags := flag.NewFlagSet(name, errorHandling)
84
+	flags.Usage = func() {
85
+		flags.ShortUsage()
86
+		flags.PrintDefaults()
87
+	}
88
+	flags.ShortUsage = func() {
89
+		options := ""
90
+		if signature != "" {
91
+			signature = " " + signature
92
+		}
93
+		if flags.FlagCountUndeprecated() > 0 {
94
+			options = " [OPTIONS]"
95
+		}
96
+		fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
97
+		flags.SetOutput(cli.out)
98
+	}
99
+	return flags
100
+}
101
+
102
+func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
103
+	if stream != nil {
104
+		defer stream.Close()
105
+	}
106
+	if err != nil {
107
+		return nil, statusCode, err
108
+	}
109
+	body, err := ioutil.ReadAll(stream)
110
+	if err != nil {
111
+		return nil, -1, err
112
+	}
113
+	return body, statusCode, nil
114
+}
0 115
new file mode 100644
... ...
@@ -0,0 +1,231 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"net/http"
7
+	"text/tabwriter"
8
+
9
+	flag "github.com/docker/docker/pkg/mflag"
10
+	"github.com/docker/docker/pkg/stringid"
11
+)
12
+
13
+type command struct {
14
+	name        string
15
+	description string
16
+}
17
+
18
+var (
19
+	networkCommands = []command{
20
+		{"create", "Create a network"},
21
+		{"rm", "Remove a network"},
22
+		{"ls", "List all networks"},
23
+		{"info", "Display information of a network"},
24
+	}
25
+)
26
+
27
+// CmdNetwork handles the root Network UI
28
+func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error {
29
+	cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false)
30
+	cmd.Require(flag.Min, 1)
31
+	err := cmd.ParseFlags(args, true)
32
+	if err == nil {
33
+		cmd.Usage()
34
+		return fmt.Errorf("invalid command : %v", args)
35
+	}
36
+	return err
37
+}
38
+
39
+// CmdNetworkCreate handles Network Create UI
40
+func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
41
+	cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false)
42
+	flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
43
+	cmd.Require(flag.Exact, 1)
44
+	err := cmd.ParseFlags(args, true)
45
+	if err != nil {
46
+		return err
47
+	}
48
+
49
+	// Construct network create request body
50
+	ops := make(map[string]interface{})
51
+	nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Options: ops}
52
+	obj, _, err := readBody(cli.call("POST", "/networks", nc, nil))
53
+	if err != nil {
54
+		return err
55
+	}
56
+	var replyID string
57
+	err = json.Unmarshal(obj, &replyID)
58
+	if err != nil {
59
+		return err
60
+	}
61
+	fmt.Fprintf(cli.out, "%s\n", replyID)
62
+	return nil
63
+}
64
+
65
+// CmdNetworkRm handles Network Delete UI
66
+func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
67
+	cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false)
68
+	cmd.Require(flag.Exact, 1)
69
+	err := cmd.ParseFlags(args, true)
70
+	if err != nil {
71
+		return err
72
+	}
73
+	id, err := lookupNetworkID(cli, cmd.Arg(0))
74
+	if err != nil {
75
+		return err
76
+	}
77
+	_, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil))
78
+	if err != nil {
79
+		return err
80
+	}
81
+	return nil
82
+}
83
+
84
+// CmdNetworkLs handles Network List UI
85
+func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
86
+	cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false)
87
+	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
88
+	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
89
+	nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
90
+	last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
91
+	err := cmd.ParseFlags(args, true)
92
+	if err != nil {
93
+		return err
94
+	}
95
+	obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
96
+	if err != nil {
97
+		return err
98
+	}
99
+	if *last == -1 && *nLatest {
100
+		*last = 1
101
+	}
102
+
103
+	var networkResources []networkResource
104
+	err = json.Unmarshal(obj, &networkResources)
105
+	if err != nil {
106
+		return err
107
+	}
108
+
109
+	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
110
+
111
+	// unless quiet (-q) is specified, print field titles
112
+	if !*quiet {
113
+		fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE")
114
+	}
115
+
116
+	for _, networkResource := range networkResources {
117
+		ID := networkResource.ID
118
+		netName := networkResource.Name
119
+		if !*noTrunc {
120
+			ID = stringid.TruncateID(ID)
121
+		}
122
+		if *quiet {
123
+			fmt.Fprintln(wr, ID)
124
+			continue
125
+		}
126
+		netType := networkResource.Type
127
+		fmt.Fprintf(wr, "%s\t%s\t%s\t",
128
+			ID,
129
+			netName,
130
+			netType)
131
+		fmt.Fprint(wr, "\n")
132
+	}
133
+	wr.Flush()
134
+	return nil
135
+}
136
+
137
+// CmdNetworkInfo handles Network Info UI
138
+func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
139
+	cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false)
140
+	cmd.Require(flag.Exact, 1)
141
+	err := cmd.ParseFlags(args, true)
142
+	if err != nil {
143
+		return err
144
+	}
145
+
146
+	id, err := lookupNetworkID(cli, cmd.Arg(0))
147
+	if err != nil {
148
+		return err
149
+	}
150
+
151
+	obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil))
152
+	if err != nil {
153
+		return err
154
+	}
155
+	networkResource := &networkResource{}
156
+	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
157
+		return err
158
+	}
159
+	fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
160
+	fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
161
+	fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
162
+	if networkResource.Services != nil {
163
+		for _, serviceResource := range networkResource.Services {
164
+			fmt.Fprintf(cli.out, "  Service Id: %s\n", serviceResource.ID)
165
+			fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
166
+		}
167
+	}
168
+
169
+	return nil
170
+}
171
+
172
+// Helper function to predict if a string is a name or id or partial-id
173
+// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs
174
+// Being a UI, its most likely that name will be used by the user, which is used to lookup
175
+// the corresponding ID. If ID is not found, this function will assume that the passed string
176
+// is an ID by itself.
177
+
178
+func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) {
179
+	obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil))
180
+	if err != nil {
181
+		return "", err
182
+	}
183
+
184
+	if statusCode != http.StatusOK {
185
+		return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
186
+	}
187
+
188
+	var list []*networkResource
189
+	err = json.Unmarshal(obj, &list)
190
+	if err != nil {
191
+		return "", err
192
+	}
193
+	if len(list) > 0 {
194
+		// name query filter will always return a single-element collection
195
+		return list[0].ID, nil
196
+	}
197
+
198
+	// Check for Partial-id
199
+	obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil))
200
+	if err != nil {
201
+		return "", err
202
+	}
203
+
204
+	if statusCode != http.StatusOK {
205
+		return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
206
+	}
207
+
208
+	err = json.Unmarshal(obj, &list)
209
+	if err != nil {
210
+		return "", err
211
+	}
212
+	if len(list) == 0 {
213
+		return "", fmt.Errorf("resource not found %s", nameID)
214
+	}
215
+	if len(list) > 1 {
216
+		return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID)
217
+	}
218
+	return list[0].ID, nil
219
+}
220
+
221
+func networkUsage(chain string) string {
222
+	help := "Commands:\n"
223
+
224
+	for _, cmd := range networkCommands {
225
+		help += fmt.Sprintf("    %-25.25s%s\n", cmd.name, cmd.description)
226
+	}
227
+
228
+	help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
229
+	return help
230
+}
0 231
new file mode 100644
... ...
@@ -0,0 +1,362 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"net/http"
7
+	"strings"
8
+	"text/tabwriter"
9
+
10
+	flag "github.com/docker/docker/pkg/mflag"
11
+	"github.com/docker/docker/pkg/stringid"
12
+)
13
+
14
+var (
15
+	serviceCommands = []command{
16
+		{"publish", "Publish a service"},
17
+		{"unpublish", "Remove a service"},
18
+		{"attach", "Attach a backend (container) to the service"},
19
+		{"detach", "Detach the backend from the service"},
20
+		{"ls", "Lists all services"},
21
+		{"info", "Display information about a service"},
22
+	}
23
+)
24
+
25
+func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
26
+	// Sanity Check
27
+	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
28
+	if err != nil {
29
+		return "", err
30
+	}
31
+	var nwList []networkResource
32
+	if err = json.Unmarshal(obj, &nwList); err != nil {
33
+		return "", err
34
+	}
35
+	if len(nwList) == 0 {
36
+		return "", fmt.Errorf("Network %s does not exist", nwName)
37
+	}
38
+
39
+	if nwName == "" {
40
+		obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
41
+		if err != nil {
42
+			return "", err
43
+		}
44
+		networkResource := &networkResource{}
45
+		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
46
+			return "", err
47
+		}
48
+		nwName = networkResource.Name
49
+	}
50
+
51
+	// Query service by name
52
+	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
53
+	if err != nil {
54
+		return "", err
55
+	}
56
+
57
+	if statusCode != http.StatusOK {
58
+		return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
59
+	}
60
+
61
+	var list []*serviceResource
62
+	if err = json.Unmarshal(obj, &list); err != nil {
63
+		return "", err
64
+	}
65
+	for _, sr := range list {
66
+		if sr.Network == nwName {
67
+			return sr.ID, nil
68
+		}
69
+	}
70
+
71
+	// Query service by Partial-id (this covers full id as well)
72
+	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
73
+	if err != nil {
74
+		return "", err
75
+	}
76
+
77
+	if statusCode != http.StatusOK {
78
+		return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
79
+	}
80
+
81
+	if err = json.Unmarshal(obj, &list); err != nil {
82
+		return "", err
83
+	}
84
+	for _, sr := range list {
85
+		if sr.Network == nwName {
86
+			return sr.ID, nil
87
+		}
88
+	}
89
+
90
+	return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
91
+}
92
+
93
+func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
94
+	// Container is a Docker resource, ask docker about it.
95
+	// In case of connecton error, we assume we are running in dnet and return whatever was passed to us
96
+	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
97
+	if err != nil {
98
+		// We are probably running outside of docker
99
+		return cnNameID, nil
100
+	}
101
+
102
+	var x map[string]interface{}
103
+	err = json.Unmarshal(obj, &x)
104
+	if err != nil {
105
+		return "", err
106
+	}
107
+	if iid, ok := x["Id"]; ok {
108
+		if id, ok := iid.(string); ok {
109
+			return id, nil
110
+		}
111
+		return "", fmt.Errorf("Unexpected data type for container ID in json response")
112
+	}
113
+	return "", fmt.Errorf("Cannot find container ID in json response")
114
+}
115
+
116
+// CmdService handles the service UI
117
+func (cli *NetworkCli) CmdService(chain string, args ...string) error {
118
+	cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
119
+	cmd.Require(flag.Min, 1)
120
+	err := cmd.ParseFlags(args, true)
121
+	if err == nil {
122
+		cmd.Usage()
123
+		return fmt.Errorf("Invalid command : %v", args)
124
+	}
125
+	return err
126
+}
127
+
128
+// Parse service name for "SERVICE[.NETWORK]" format
129
+func parseServiceName(name string) (string, string) {
130
+	s := strings.Split(name, ".")
131
+	var sName, nName string
132
+	if len(s) > 1 {
133
+		nName = s[len(s)-1]
134
+		sName = strings.Join(s[:len(s)-1], ".")
135
+	} else {
136
+		sName = s[0]
137
+	}
138
+	return sName, nName
139
+}
140
+
141
+// CmdServicePublish handles service create UI
142
+func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
143
+	cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
144
+	cmd.Require(flag.Exact, 1)
145
+	err := cmd.ParseFlags(args, true)
146
+	if err != nil {
147
+		return err
148
+	}
149
+
150
+	sn, nn := parseServiceName(cmd.Arg(0))
151
+	sc := serviceCreate{Name: sn, Network: nn}
152
+	obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
153
+	if err != nil {
154
+		return err
155
+	}
156
+
157
+	var replyID string
158
+	err = json.Unmarshal(obj, &replyID)
159
+	if err != nil {
160
+		return err
161
+	}
162
+
163
+	fmt.Fprintf(cli.out, "%s\n", replyID)
164
+	return nil
165
+}
166
+
167
+// CmdServiceUnpublish handles service delete UI
168
+func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
169
+	cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
170
+	cmd.Require(flag.Exact, 1)
171
+	err := cmd.ParseFlags(args, true)
172
+	if err != nil {
173
+		return err
174
+	}
175
+
176
+	sn, nn := parseServiceName(cmd.Arg(0))
177
+	serviceID, err := lookupServiceID(cli, nn, sn)
178
+	if err != nil {
179
+		return err
180
+	}
181
+
182
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, nil, nil))
183
+
184
+	return err
185
+}
186
+
187
+// CmdServiceLs handles service list UI
188
+func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
189
+	cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
190
+	flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
191
+	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
192
+	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
193
+
194
+	err := cmd.ParseFlags(args, true)
195
+	if err != nil {
196
+		return err
197
+	}
198
+
199
+	var obj []byte
200
+	if *flNetwork == "" {
201
+		obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
202
+	} else {
203
+		obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
204
+	}
205
+	if err != nil {
206
+		return err
207
+	}
208
+
209
+	var serviceResources []serviceResource
210
+	err = json.Unmarshal(obj, &serviceResources)
211
+	if err != nil {
212
+		fmt.Println(err)
213
+		return err
214
+	}
215
+
216
+	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
217
+	// unless quiet (-q) is specified, print field titles
218
+	if !*quiet {
219
+		fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER")
220
+	}
221
+
222
+	for _, sr := range serviceResources {
223
+		ID := sr.ID
224
+		bkID, err := getBackendID(cli, ID)
225
+		if err != nil {
226
+			return err
227
+		}
228
+		if !*noTrunc {
229
+			ID = stringid.TruncateID(ID)
230
+			bkID = stringid.TruncateID(bkID)
231
+		}
232
+		if !*quiet {
233
+			fmt.Fprintf(wr, "%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID)
234
+		} else {
235
+			fmt.Fprintln(wr, ID)
236
+		}
237
+	}
238
+	wr.Flush()
239
+
240
+	return nil
241
+}
242
+
243
+func getBackendID(cli *NetworkCli, servID string) (string, error) {
244
+	var (
245
+		obj []byte
246
+		err error
247
+		bk  string
248
+	)
249
+
250
+	if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
251
+		var bkl []backendResource
252
+		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&bkl); err == nil {
253
+			if len(bkl) > 0 {
254
+				bk = bkl[0].ID
255
+			}
256
+		} else {
257
+			// Only print a message, don't make the caller cli fail for this
258
+			fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)", servID, err)
259
+		}
260
+	}
261
+
262
+	return bk, err
263
+}
264
+
265
+// CmdServiceInfo handles service info UI
266
+func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
267
+	cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false)
268
+	cmd.Require(flag.Min, 1)
269
+
270
+	err := cmd.ParseFlags(args, true)
271
+	if err != nil {
272
+		return err
273
+	}
274
+
275
+	sn, nn := parseServiceName(cmd.Arg(0))
276
+	serviceID, err := lookupServiceID(cli, nn, sn)
277
+	if err != nil {
278
+		return err
279
+	}
280
+
281
+	obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
282
+	if err != nil {
283
+		return err
284
+	}
285
+
286
+	sr := &serviceResource{}
287
+	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
288
+		return err
289
+	}
290
+
291
+	fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
292
+	fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
293
+	fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
294
+
295
+	return nil
296
+}
297
+
298
+// CmdServiceAttach handles service attach UI
299
+func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
300
+	cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
301
+	cmd.Require(flag.Min, 2)
302
+	err := cmd.ParseFlags(args, true)
303
+	if err != nil {
304
+		return err
305
+	}
306
+
307
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
308
+	if err != nil {
309
+		return err
310
+	}
311
+
312
+	sn, nn := parseServiceName(cmd.Arg(1))
313
+	serviceID, err := lookupServiceID(cli, nn, sn)
314
+	if err != nil {
315
+		return err
316
+	}
317
+
318
+	nc := serviceAttach{ContainerID: containerID}
319
+
320
+	_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
321
+
322
+	return err
323
+}
324
+
325
+// CmdServiceDetach handles service detach UI
326
+func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
327
+	cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
328
+	cmd.Require(flag.Min, 2)
329
+	err := cmd.ParseFlags(args, true)
330
+	if err != nil {
331
+		return err
332
+	}
333
+
334
+	sn, nn := parseServiceName(cmd.Arg(1))
335
+	containerID, err := lookupContainerID(cli, cmd.Arg(0))
336
+	if err != nil {
337
+		return err
338
+	}
339
+
340
+	serviceID, err := lookupServiceID(cli, nn, sn)
341
+	if err != nil {
342
+		return err
343
+	}
344
+
345
+	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+containerID, nil, nil))
346
+	if err != nil {
347
+		return err
348
+	}
349
+	return nil
350
+}
351
+
352
+func serviceUsage(chain string) string {
353
+	help := "Commands:\n"
354
+
355
+	for _, cmd := range serviceCommands {
356
+		help += fmt.Sprintf("    %-10.10s%s\n", cmd.name, cmd.description)
357
+	}
358
+
359
+	help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain)
360
+	return help
361
+}
0 362
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package client
1
+
2
+import "github.com/docker/libnetwork/types"
3
+
4
+/***********
5
+ Resources
6
+************/
7
+
8
+// networkResource is the body of the "get network" http response message
9
+type networkResource struct {
10
+	Name     string             `json:"name"`
11
+	ID       string             `json:"id"`
12
+	Type     string             `json:"type"`
13
+	Services []*serviceResource `json:"services"`
14
+}
15
+
16
+// serviceResource is the body of the "get service" http response message
17
+type serviceResource struct {
18
+	Name    string `json:"name"`
19
+	ID      string `json:"id"`
20
+	Network string `json:"network"`
21
+}
22
+
23
+// backendResource is the body of "get service backend" response message
24
+type backendResource struct {
25
+	ID string `json:"id"`
26
+}
27
+
28
+/***********
29
+  Body types
30
+  ************/
31
+
32
+// networkCreate is the expected body of the "create network" http request message
33
+type networkCreate struct {
34
+	Name        string                 `json:"name"`
35
+	NetworkType string                 `json:"network_type"`
36
+	Options     map[string]interface{} `json:"options"`
37
+}
38
+
39
+// serviceCreate represents the body of the "publish service" http request message
40
+type serviceCreate struct {
41
+	Name         string                `json:"name"`
42
+	Network      string                `json:"network_name"`
43
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
44
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
45
+}
46
+
47
+// serviceAttach represents the expected body of the "attach/detach backend to/from service" http request messages
48
+type serviceAttach struct {
49
+	ContainerID       string                `json:"container_id"`
50
+	HostName          string                `json:"host_name"`
51
+	DomainName        string                `json:"domain_name"`
52
+	HostsPath         string                `json:"hosts_path"`
53
+	ResolvConfPath    string                `json:"resolv_conf_path"`
54
+	DNS               []string              `json:"dns"`
55
+	ExtraHosts        []serviceExtraHost    `json:"extra_hosts"`
56
+	ParentUpdates     []serviceParentUpdate `json:"parent_updates"`
57
+	UseDefaultSandbox bool                  `json:"use_default_sandbox"`
58
+}
59
+
60
+// serviceExtraHost represents the extra host object
61
+type serviceExtraHost struct {
62
+	Name    string `json:"name"`
63
+	Address string `json:"address"`
64
+}
65
+
66
+// EndpointParentUpdate is the object carrying the information about the
67
+// endpoint parent that needs to be updated
68
+type serviceParentUpdate struct {
69
+	EndpointID string `json:"service_id"`
70
+	Name       string `json:"name"`
71
+	Address    string `json:"address"`
72
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"strings"
5 5
 
6 6
 	"github.com/BurntSushi/toml"
7
+	"github.com/docker/libnetwork/netlabel"
7 8
 )
8 9
 
9 10
 // Config encapsulates configurations of various Libnetwork components
... ...
@@ -18,6 +19,7 @@ type DaemonCfg struct {
18 18
 	Debug          bool
19 19
 	DefaultNetwork string
20 20
 	DefaultDriver  string
21
+	Labels         []string
21 22
 }
22 23
 
23 24
 // ClusterCfg represents cluster configuration
... ...
@@ -66,6 +68,17 @@ func OptionDefaultDriver(dd string) Option {
66 66
 	}
67 67
 }
68 68
 
69
+// OptionLabels function returns an option setter for labels
70
+func OptionLabels(labels []string) Option {
71
+	return func(c *Config) {
72
+		for _, label := range labels {
73
+			if strings.HasPrefix(label, netlabel.Prefix) {
74
+				c.Daemon.Labels = append(c.Daemon.Labels, label)
75
+			}
76
+		}
77
+	}
78
+}
79
+
69 80
 // OptionKVProvider function returns an option setter for kvstore provider
70 81
 func OptionKVProvider(provider string) Option {
71 82
 	return func(c *Config) {
... ...
@@ -88,3 +101,11 @@ func (c *Config) ProcessOptions(options ...Option) {
88 88
 		}
89 89
 	}
90 90
 }
91
+
92
+// IsValidName validates configuration objects supported by libnetwork
93
+func IsValidName(name string) bool {
94
+	if name == "" || strings.Contains(name, ".") {
95
+		return false
96
+	}
97
+	return true
98
+}
... ...
@@ -169,6 +169,9 @@ func (c *controller) hostLeaveCallback(hosts []net.IP) {
169 169
 func (c *controller) Config() config.Config {
170 170
 	c.Lock()
171 171
 	defer c.Unlock()
172
+	if c.cfg == nil {
173
+		return config.Config{}
174
+	}
172 175
 	return *c.cfg
173 176
 }
174 177
 
... ...
@@ -185,6 +188,9 @@ func (c *controller) ConfigureNetworkDriver(networkType string, options map[stri
185 185
 func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error {
186 186
 	c.Lock()
187 187
 	defer c.Unlock()
188
+	if !config.IsValidName(networkType) {
189
+		return ErrInvalidName(networkType)
190
+	}
188 191
 	if _, ok := c.drivers[networkType]; ok {
189 192
 		return driverapi.ErrActiveRegistration(networkType)
190 193
 	}
... ...
@@ -195,7 +201,7 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver,
195 195
 // NewNetwork creates a new network of the specified network type. The options
196 196
 // are network specific and modeled in a generic way.
197 197
 func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {
198
-	if name == "" {
198
+	if !config.IsValidName(name) {
199 199
 		return nil, ErrInvalidName(name)
200 200
 	}
201 201
 	// Check if a network already exists with the specified network name
... ...
@@ -1,18 +1,24 @@
1 1
 package netlabel
2 2
 
3 3
 const (
4
+	// Prefix constant marks the reserved label space for libnetwork
5
+	Prefix = "com.docker.network"
6
+
7
+	// DriverPrefix constant marks the reserved label space for libnetwork drivers
8
+	DriverPrefix = Prefix + ".driver"
9
+
4 10
 	// GenericData constant that helps to identify an option as a Generic constant
5
-	GenericData = "io.docker.network.generic"
11
+	GenericData = Prefix + ".generic"
6 12
 
7 13
 	// PortMap constant represents Port Mapping
8
-	PortMap = "io.docker.network.endpoint.portmap"
14
+	PortMap = Prefix + ".portmap"
9 15
 
10 16
 	// MacAddress constant represents Mac Address config of a Container
11
-	MacAddress = "io.docker.network.endpoint.macaddress"
17
+	MacAddress = Prefix + ".endpoint.macaddress"
12 18
 
13 19
 	// ExposedPorts constant represents exposedports of a Container
14
-	ExposedPorts = "io.docker.network.endpoint.exposedports"
20
+	ExposedPorts = Prefix + ".endpoint.exposedports"
15 21
 
16 22
 	//EnableIPv6 constant represents enabling IPV6 at network level
17
-	EnableIPv6 = "io.docker.network.enable_ipv6"
23
+	EnableIPv6 = Prefix + ".enable_ipv6"
18 24
 )
... ...
@@ -6,6 +6,7 @@ import (
6 6
 
7 7
 	log "github.com/Sirupsen/logrus"
8 8
 	"github.com/docker/docker/pkg/stringid"
9
+	"github.com/docker/libnetwork/config"
9 10
 	"github.com/docker/libnetwork/datastore"
10 11
 	"github.com/docker/libnetwork/driverapi"
11 12
 	"github.com/docker/libnetwork/netlabel"
... ...
@@ -274,7 +275,7 @@ func (n *network) addEndpoint(ep *endpoint) error {
274 274
 
275 275
 func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
276 276
 	var err error
277
-	if name == "" {
277
+	if !config.IsValidName(name) {
278 278
 		return nil, ErrInvalidName(name)
279 279
 	}
280 280