Pārlūkot izejas kodu

Merge pull request #14051 from mavenugo/services

experimental services ui
Arnaud Porterie 10 gadi atpakaļ
vecāks
revīzija
fef0e84b2d

+ 15 - 0
api/client/service.go

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

+ 5 - 0
api/server/server_experimental.go

@@ -9,4 +9,9 @@ func (s *Server) registerSubRouter() {
 	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
 	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
 	subrouter = s.router.PathPrefix("/networks").Subrouter()
 	subrouter = s.router.PathPrefix("/networks").Subrouter()
 	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
 	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
+
+	subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter()
+	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
+	subrouter = s.router.PathPrefix("/services").Subrouter()
+	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
 }
 }

+ 47 - 13
daemon/container_linux.go

@@ -737,11 +737,23 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
 	return createOptions, nil
 	return createOptions, nil
 }
 }
 
 
-func createDefaultNetwork(controller libnetwork.NetworkController) (libnetwork.Network, error) {
+func parseService(controller libnetwork.NetworkController, service string) (string, string, string) {
+	dn := controller.Config().Daemon.DefaultNetwork
+	dd := controller.Config().Daemon.DefaultDriver
+
+	snd := strings.Split(service, ".")
+	if len(snd) > 2 {
+		return strings.Join(snd[:len(snd)-2], "."), snd[len(snd)-2], snd[len(snd)-1]
+	}
+	if len(snd) > 1 {
+		return snd[0], snd[1], dd
+	}
+	return snd[0], dn, dd
+}
+
+func createNetwork(controller libnetwork.NetworkController, dnet string, driver string) (libnetwork.Network, error) {
 	createOptions := []libnetwork.NetworkOption{}
 	createOptions := []libnetwork.NetworkOption{}
 	genericOption := options.Generic{}
 	genericOption := options.Generic{}
-	dnet := controller.Config().Daemon.DefaultNetwork
-	driver := controller.Config().Daemon.DefaultDriver
 
 
 	// Bridge driver is special due to legacy reasons
 	// Bridge driver is special due to legacy reasons
 	if runconfig.NetworkMode(driver).IsBridge() {
 	if runconfig.NetworkMode(driver).IsBridge() {
@@ -763,31 +775,53 @@ func (container *Container) AllocateNetwork() error {
 		return nil
 		return nil
 	}
 	}
 
 
+	var networkDriver string
+	service := container.Config.PublishService
 	networkName := mode.NetworkName()
 	networkName := mode.NetworkName()
 	if mode.IsDefault() {
 	if mode.IsDefault() {
-		networkName = controller.Config().Daemon.DefaultNetwork
+		if service != "" {
+			service, networkName, networkDriver = parseService(controller, service)
+		} else {
+			networkName = controller.Config().Daemon.DefaultNetwork
+			networkDriver = controller.Config().Daemon.DefaultDriver
+		}
+	} else if service != "" {
+		return fmt.Errorf("conflicting options: publishing a service and network mode")
+	}
+
+	if service == "" {
+		service = strings.Replace(container.Name, ".", "-", -1)
 	}
 	}
 
 
 	var err error
 	var err error
 
 
 	n, err := controller.NetworkByName(networkName)
 	n, err := controller.NetworkByName(networkName)
 	if err != nil {
 	if err != nil {
-		if !mode.IsDefault() {
-			return fmt.Errorf("error locating network with name %s: %v", networkName, err)
+		// Create Network automatically only in default mode
+		if _, ok := err.(libnetwork.ErrNoSuchNetwork); !ok || !mode.IsDefault() {
+			return err
 		}
 		}
-		if n, err = createDefaultNetwork(controller); err != nil {
+
+		if n, err = createNetwork(controller, networkName, networkDriver); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
-	createOptions, err := container.buildCreateEndpointOptions()
+	ep, err := n.EndpointByName(service)
 	if err != nil {
 	if err != nil {
-		return err
-	}
+		if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok {
+			return err
+		}
 
 
-	ep, err := n.CreateEndpoint(container.Name, createOptions...)
-	if err != nil {
-		return err
+		createOptions, err := container.buildCreateEndpointOptions()
+		if err != nil {
+			return err
+		}
+
+		ep, err = n.CreateEndpoint(service, createOptions...)
+		if err != nil {
+			return err
+		}
 	}
 	}
 
 
 	if err := container.updateNetworkSettings(n, ep); err != nil {
 	if err := container.updateNetworkSettings(n, ep); err != nil {

+ 45 - 3
experimental/networking.md

@@ -2,7 +2,10 @@
 
 
 In this feature:
 In this feature:
 
 
-- `network` become a first class objects in the Docker UI
+- `network` and `service` become a first class objects in the Docker UI
+- You can create networks and attach containers to them
+- We introduce the concept of `services`
+  - This is an entry-point in to a given network that is also published via Service Discovery
 
 
 This is an experimental feature. For information on installing and using experimental features, see [the experimental feature overview](experimental.md).
 This is an experimental feature. For information on installing and using experimental features, see [the experimental feature overview](experimental.md).
 
 
@@ -59,14 +62,53 @@ If you no longer have need of a network, you can delete it with `docker network
         bd61375b6993        host                host
         bd61375b6993        host                host
         cc455abccfeb        bridge              bridge
         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,
 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 
 `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.
 `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
 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.
 the container. If `--default-network` is not specified, the default network will be the `bridge` driver.
 
 
+## Using Services
+
+        Usage: docker service COMMAND [OPTIONS] [arg...]
+
+        Commands:
+            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
+
+        Run 'docker service COMMAND --help' for more information on a command.
+
+          --help=false       Print usage
+
+Assuming we want to publish a service from container `a0ebc12d3e48` on network `foo` as `my-service` we would use the following command:
+
+        $ docker service publish my-service.foo
+        ec56fd74717d00f968c26675c9a77707e49ae64b8e54832ebf78888eb116e428
+        $ docker service attach a0ebc12d3e48 my-service.foo
+
+This would make the container `a0ebc12d3e48` accessible as `my-service` on network `foo`. Any other container in network `foo` can use DNS to resolve the address of `my-service`
+
+This can also be acheived by using the `--publish-service` flag for `docker run`:
+
+        docker run -itd --publish-service db.foo postgres
+
+`db.foo` in this instance means "place the container on network `foo`, and allow other hosts on `foo` to discover it under the name `db`"
+
+We can see the current services using the `docker service ls` command
+
+        $ docker service ls
+        SERVICE ID          NAME                NETWORK             PROVIDER
+        ec56fd74717d        my-service          foo                 a0ebc12d3e48
+
+To remove the a service:
+
+        $ docker service detach a0ebc12d3e48 my-service.foo
+        $ docker service unpublish my-service.foo
+
 Send us feedback and comments on [#](https://github.com/docker/docker/issues/?),
 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.
 or on the usual Google Groups (docker-user, docker-dev) and IRC channels.
 
 

+ 202 - 0
experimental/networking_api.md

@@ -285,3 +285,205 @@ Status Codes:
 -   **200** – no error
 -   **200** – no error
 -   **404** – not found
 -   **404** – not found
 -   **500** – server error
 -   **500** – server error
+
+# Services API
+
+### Publish a Service
+
+`POST /services`
+
+Publish a service
+
+**Example Request**
+
+        POST /services HTTP/1.1
+        Content-Type: application/json
+
+        {
+          "name": "bar",
+          "network_name": "foo",
+          "exposed_ports": null,
+          "port_mapping": null
+        }
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        "0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff"
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Get a Service
+
+`GET /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff`
+
+Get a service
+
+**Example Request**:
+
+        GET /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff HTTP/1.1
+
+**Example Response**:
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        {
+          "name": "bar",
+          "id": "0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff",
+          "network": "foo"
+        }
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **404** - not found
+-   **500** – server error
+
+### Attach a backend to a service
+
+`POST /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff/backend`
+
+Attach a backend to a service
+
+**Example Request**:
+
+        POST /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff/backend HTTP/1.1
+        Content-Type: application/json
+
+        {
+          "container_id": "98c5241f9475e9efc17e7198e931fb48166010b80f96d48df204e251378ca547",
+          "host_name": "",
+          "domain_name": "",
+          "hosts_path": "",
+          "resolv_conf_path": "",
+          "dns": null,
+          "extra_hosts": null,
+          "parent_updates": null,
+          "use_default_sandbox": false
+        }
+
+**Example Response**:
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        "/var/run/docker/netns/98c5241f9475"
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Get Backends for a Service
+
+Get all backends for a given service
+
+**Example Request**
+
+        GET /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff/backend HTTP/1.1
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        [
+          {
+            "id": "98c5241f9475e9efc17e7198e931fb48166010b80f96d48df204e251378ca547"
+          }
+        ]
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### List Services
+
+`GET /services`
+
+List services
+
+**Example request**:
+
+        GET /services HTTP/1.1
+
+**Example response**:
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+
+        [
+          {
+            "name": "/stupefied_stallman",
+            "id": "c826b26bf736fb4a77db33f83562e59f9a770724e259ab9c3d50d948f8233ae4",
+            "network": "bridge"
+          },
+          {
+            "name": "bar",
+            "id": "0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff",
+            "network": "foo"
+          }
+        ]
+
+Query Parameters:
+
+-   **name** – Filter results with the given name
+-   **partial-id** – Filter results using the partial network ID
+-   **network** - Filter results by the given network
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Detach a Backend from a Service
+
+`DELETE /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff/backend/98c5241f9475e9efc17e7198e931fb48166010b80f96d48df204e251378ca547`
+
+Detach a backend from a service
+
+**Example Request**
+
+        DELETE /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff/backend/98c5241f9475e9efc17e7198e931fb48166010b80f96d48df204e251378ca547 HTTP/1.1
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error
+
+### Un-Publish a Service
+
+`DELETE /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff`
+
+Unpublish a service
+
+**Example Request**
+
+        DELETE /services/0aee0899e6c5e903cf3ef2bdc28a1c9aaf639c8c8c331fa4ae26344d9e32c1ff HTTP/1.1
+
+**Example Response**
+
+        HTTP/1.1 200 OK
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **500** – server error

+ 113 - 0
integration-cli/docker_api_service_test.go

@@ -0,0 +1,113 @@
+// +build experimental
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/go-check/check"
+)
+
+func isServiceAvailable(c *check.C, name string, network string) bool {
+	status, body, err := sockRequest("GET", "/services", nil)
+	c.Assert(status, check.Equals, http.StatusOK)
+	c.Assert(err, check.IsNil)
+
+	var inspectJSON []struct {
+		Name    string
+		ID      string
+		Network string
+	}
+	if err = json.Unmarshal(body, &inspectJSON); err != nil {
+		c.Fatalf("unable to unmarshal response body: %v", err)
+	}
+	for _, s := range inspectJSON {
+		if s.Name == name && s.Network == network {
+			return true
+		}
+	}
+	return false
+
+}
+
+func isServiceNetworkAvailable(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) TestServiceApiCreateDelete(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 !isServiceNetworkAvailable(c, name) {
+		c.Fatalf("Network %s not found", name)
+	}
+
+	var nid string
+	err = json.Unmarshal(resp, &nid)
+	if err != nil {
+		c.Fatal(err)
+	}
+
+	sname := "service1"
+	sconfig := map[string]interface{}{
+		"name":         sname,
+		"network_name": name,
+	}
+
+	status, resp, err = sockRequest("POST", "/services", sconfig)
+	c.Assert(status, check.Equals, http.StatusCreated)
+	c.Assert(err, check.IsNil)
+
+	if !isServiceAvailable(c, sname, name) {
+		c.Fatalf("Service %s.%s not found", sname, name)
+	}
+
+	var id string
+	err = json.Unmarshal(resp, &id)
+	if err != nil {
+		c.Fatal(err)
+	}
+
+	status, _, err = sockRequest("DELETE", fmt.Sprintf("/services/%s", id), nil)
+	c.Assert(status, check.Equals, http.StatusOK)
+	c.Assert(err, check.IsNil)
+
+	if isServiceAvailable(c, sname, name) {
+		c.Fatalf("Service %s.%s not deleted", sname, name)
+	}
+
+	status, _, err = sockRequest("DELETE", fmt.Sprintf("/networks/%s", nid), nil)
+	c.Assert(status, check.Equals, http.StatusOK)
+	c.Assert(err, check.IsNil)
+
+	if isNetworkAvailable(c, name) {
+		c.Fatalf("Network %s not deleted", name)
+	}
+}

+ 21 - 21
integration-cli/docker_cli_network_test.go

@@ -9,12 +9,22 @@ import (
 	"github.com/go-check/check"
 	"github.com/go-check/check"
 )
 )
 
 
-func isNetworkPresent(c *check.C, name string) bool {
+func assertNwIsAvailable(c *check.C, name string) {
+	if !isNwPresent(c, name) {
+		c.Fatalf("Network %s not found in network ls o/p", name)
+	}
+}
+
+func assertNwNotAvailable(c *check.C, name string) {
+	if isNwPresent(c, name) {
+		c.Fatalf("Found network %s in network ls o/p", name)
+	}
+}
+
+func isNwPresent(c *check.C, name string) bool {
 	runCmd := exec.Command(dockerBinary, "network", "ls")
 	runCmd := exec.Command(dockerBinary, "network", "ls")
 	out, _, _, err := runCommandWithStdoutStderr(runCmd)
 	out, _, _, err := runCommandWithStdoutStderr(runCmd)
-	if err != nil {
-		c.Fatal(out, err)
-	}
+	c.Assert(err, check.IsNil)
 	lines := strings.Split(out, "\n")
 	lines := strings.Split(out, "\n")
 	for i := 1; i < len(lines)-1; i++ {
 	for i := 1; i < len(lines)-1; i++ {
 		if strings.Contains(lines[i], name) {
 		if strings.Contains(lines[i], name) {
@@ -27,28 +37,18 @@ func isNetworkPresent(c *check.C, name string) bool {
 func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) {
 func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) {
 	defaults := []string{"bridge", "host", "none"}
 	defaults := []string{"bridge", "host", "none"}
 	for _, nn := range defaults {
 	for _, nn := range defaults {
-		if !isNetworkPresent(c, nn) {
-			c.Fatalf("Missing Default network : %s", nn)
-		}
+		assertNwIsAvailable(c, nn)
 	}
 	}
 }
 }
 
 
 func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) {
 func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) {
 	runCmd := exec.Command(dockerBinary, "network", "create", "test")
 	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")
-	}
+	_, _, _, err := runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	assertNwIsAvailable(c, "test")
 
 
 	runCmd = exec.Command(dockerBinary, "network", "rm", "test")
 	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")
-	}
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	assertNwNotAvailable(c, "test")
 }
 }

+ 86 - 0
integration-cli/docker_cli_service_test.go

@@ -0,0 +1,86 @@
+// +build experimental
+
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"strings"
+
+	"github.com/go-check/check"
+)
+
+func assertSrvIsAvailable(c *check.C, sname, name string) {
+	if !isSrvPresent(c, sname, name) {
+		c.Fatalf("Service %s on network %s not found in service ls o/p", sname, name)
+	}
+}
+
+func assertSrvNotAvailable(c *check.C, sname, name string) {
+	if isSrvPresent(c, sname, name) {
+		c.Fatalf("Found service %s on network %s in service ls o/p", sname, name)
+	}
+}
+
+func isSrvPresent(c *check.C, sname, name string) bool {
+	runCmd := exec.Command(dockerBinary, "service", "ls")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	lines := strings.Split(out, "\n")
+	for i := 1; i < len(lines)-1; i++ {
+		if strings.Contains(lines[i], sname) && strings.Contains(lines[i], name) {
+			return true
+		}
+	}
+	return false
+}
+
+func isCntPresent(c *check.C, cname, sname, name string) bool {
+	runCmd := exec.Command(dockerBinary, "service", "ls", "--no-trunc")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	lines := strings.Split(out, "\n")
+	for i := 1; i < len(lines)-1; i++ {
+		fmt.Println(lines)
+		if strings.Contains(lines[i], name) && strings.Contains(lines[i], sname) && strings.Contains(lines[i], cname) {
+			return true
+		}
+	}
+	return false
+}
+
+func (s *DockerSuite) TestDockerServiceCreateDelete(c *check.C) {
+	runCmd := exec.Command(dockerBinary, "network", "create", "test")
+	_, _, _, err := runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	assertNwIsAvailable(c, "test")
+
+	runCmd = exec.Command(dockerBinary, "service", "publish", "s1.test")
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	assertSrvIsAvailable(c, "s1", "test")
+
+	runCmd = exec.Command(dockerBinary, "service", "unpublish", "s1.test")
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	assertSrvNotAvailable(c, "s1", "test")
+
+	runCmd = exec.Command(dockerBinary, "network", "rm", "test")
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
+	c.Assert(err, check.IsNil)
+	assertNwNotAvailable(c, "test")
+}
+
+func (s *DockerSuite) TestDockerPublishServiceFlag(c *check.C) {
+	// Run saying the container is the backend for the specified service on the specified network
+	runCmd := exec.Command(dockerBinary, "run", "-d", "--expose=23", "--publish-service", "telnet.production", "busybox", "top")
+	out, _, err := runCommandWithOutput(runCmd)
+	c.Assert(err, check.IsNil)
+	cid := strings.TrimSpace(out)
+
+	// Verify container is attached in service ps o/p
+	assertSrvIsAvailable(c, "telnet", "production")
+	runCmd = exec.Command(dockerBinary, "rm", "-f", cid)
+	out, _, err = runCommandWithOutput(runCmd)
+	c.Assert(err, check.IsNil)
+}

+ 1 - 0
runconfig/config.go

@@ -114,6 +114,7 @@ type Config struct {
 	AttachStdout    bool
 	AttachStdout    bool
 	AttachStderr    bool
 	AttachStderr    bool
 	ExposedPorts    map[nat.Port]struct{}
 	ExposedPorts    map[nat.Port]struct{}
+	PublishService  string
 	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
 	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
 	OpenStdin       bool // Open stdin
 	OpenStdin       bool // Open stdin
 	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
 	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.

+ 2 - 0
runconfig/parse_experimental.go

@@ -11,9 +11,11 @@ type experimentalFlags struct {
 func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
 func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
 	flags := make(map[string]interface{})
 	flags := make(map[string]interface{})
 	flags["volume-driver"] = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
 	flags["volume-driver"] = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
+	flags["publish-service"] = cmd.String([]string{"-publish-service"}, "", "Publish this container as a service")
 	return &experimentalFlags{flags: flags}
 	return &experimentalFlags{flags: flags}
 }
 }
 
 
 func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) {
 func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) {
 	config.VolumeDriver = *(exp.flags["volume-driver"]).(*string)
 	config.VolumeDriver = *(exp.flags["volume-driver"]).(*string)
+	config.PublishService = *(exp.flags["publish-service"]).(*string)
 }
 }