Browse code

Support for --publish-service flag in docker run

This commit makes use of the CNM model supported by LibNetwork and
provides an ability to let a container to publish a specified service.
Behind the scenes, if a service with the given name doesnt exist, it is
automatically created on appropriate network and attach the container.

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

Madhu Venugopal authored on 2015/06/15 14:31:09
Showing 5 changed files
... ...
@@ -737,11 +737,23 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
737 737
 	return createOptions, nil
738 738
 }
739 739
 
740
-func createDefaultNetwork(controller libnetwork.NetworkController) (libnetwork.Network, error) {
740
+func parseService(controller libnetwork.NetworkController, service string) (string, string, string) {
741
+	dn := controller.Config().Daemon.DefaultNetwork
742
+	dd := controller.Config().Daemon.DefaultDriver
743
+
744
+	snd := strings.Split(service, ".")
745
+	if len(snd) > 2 {
746
+		return strings.Join(snd[:len(snd)-2], "."), snd[len(snd)-2], snd[len(snd)-1]
747
+	}
748
+	if len(snd) > 1 {
749
+		return snd[0], snd[1], dd
750
+	}
751
+	return snd[0], dn, dd
752
+}
753
+
754
+func createNetwork(controller libnetwork.NetworkController, dnet string, driver string) (libnetwork.Network, error) {
741 755
 	createOptions := []libnetwork.NetworkOption{}
742 756
 	genericOption := options.Generic{}
743
-	dnet := controller.Config().Daemon.DefaultNetwork
744
-	driver := controller.Config().Daemon.DefaultDriver
745 757
 
746 758
 	// Bridge driver is special due to legacy reasons
747 759
 	if runconfig.NetworkMode(driver).IsBridge() {
... ...
@@ -763,31 +775,53 @@ func (container *Container) AllocateNetwork() error {
763 763
 		return nil
764 764
 	}
765 765
 
766
+	var networkDriver string
767
+	service := container.Config.PublishService
766 768
 	networkName := mode.NetworkName()
767 769
 	if mode.IsDefault() {
768
-		networkName = controller.Config().Daemon.DefaultNetwork
770
+		if service != "" {
771
+			service, networkName, networkDriver = parseService(controller, service)
772
+		} else {
773
+			networkName = controller.Config().Daemon.DefaultNetwork
774
+			networkDriver = controller.Config().Daemon.DefaultDriver
775
+		}
776
+	} else if service != "" {
777
+		return fmt.Errorf("conflicting options: publishing a service and network mode")
778
+	}
779
+
780
+	if service == "" {
781
+		service = strings.Replace(container.Name, ".", "-", -1)
769 782
 	}
770 783
 
771 784
 	var err error
772 785
 
773 786
 	n, err := controller.NetworkByName(networkName)
774 787
 	if err != nil {
775
-		if !mode.IsDefault() {
776
-			return fmt.Errorf("error locating network with name %s: %v", networkName, err)
788
+		// Create Network automatically only in default mode
789
+		if _, ok := err.(libnetwork.ErrNoSuchNetwork); !ok || !mode.IsDefault() {
790
+			return err
777 791
 		}
778
-		if n, err = createDefaultNetwork(controller); err != nil {
792
+
793
+		if n, err = createNetwork(controller, networkName, networkDriver); err != nil {
779 794
 			return err
780 795
 		}
781 796
 	}
782 797
 
783
-	createOptions, err := container.buildCreateEndpointOptions()
798
+	ep, err := n.EndpointByName(service)
784 799
 	if err != nil {
785
-		return err
786
-	}
800
+		if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok {
801
+			return err
802
+		}
787 803
 
788
-	ep, err := n.CreateEndpoint(container.Name, createOptions...)
789
-	if err != nil {
790
-		return err
804
+		createOptions, err := container.buildCreateEndpointOptions()
805
+		if err != nil {
806
+			return err
807
+		}
808
+
809
+		ep, err = n.CreateEndpoint(service, createOptions...)
810
+		if err != nil {
811
+			return err
812
+		}
791 813
 	}
792 814
 
793 815
 	if err := container.updateNetworkSettings(n, ep); err != nil {
... ...
@@ -9,12 +9,22 @@ import (
9 9
 	"github.com/go-check/check"
10 10
 )
11 11
 
12
-func isNetworkPresent(c *check.C, name string) bool {
12
+func assertNwIsAvailable(c *check.C, name string) {
13
+	if !isNwPresent(c, name) {
14
+		c.Fatalf("Network %s not found in network ls o/p", name)
15
+	}
16
+}
17
+
18
+func assertNwNotAvailable(c *check.C, name string) {
19
+	if isNwPresent(c, name) {
20
+		c.Fatalf("Found network %s in network ls o/p", name)
21
+	}
22
+}
23
+
24
+func isNwPresent(c *check.C, name string) bool {
13 25
 	runCmd := exec.Command(dockerBinary, "network", "ls")
14 26
 	out, _, _, err := runCommandWithStdoutStderr(runCmd)
15
-	if err != nil {
16
-		c.Fatal(out, err)
17
-	}
27
+	c.Assert(err, check.IsNil)
18 28
 	lines := strings.Split(out, "\n")
19 29
 	for i := 1; i < len(lines)-1; i++ {
20 30
 		if strings.Contains(lines[i], name) {
... ...
@@ -27,28 +37,18 @@ func isNetworkPresent(c *check.C, name string) bool {
27 27
 func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) {
28 28
 	defaults := []string{"bridge", "host", "none"}
29 29
 	for _, nn := range defaults {
30
-		if !isNetworkPresent(c, nn) {
31
-			c.Fatalf("Missing Default network : %s", nn)
32
-		}
30
+		assertNwIsAvailable(c, nn)
33 31
 	}
34 32
 }
35 33
 
36 34
 func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) {
37 35
 	runCmd := exec.Command(dockerBinary, "network", "create", "test")
38
-	out, _, _, err := runCommandWithStdoutStderr(runCmd)
39
-	if err != nil {
40
-		c.Fatal(out, err)
41
-	}
42
-	if !isNetworkPresent(c, "test") {
43
-		c.Fatalf("Network test not found")
44
-	}
36
+	_, _, _, err := runCommandWithStdoutStderr(runCmd)
37
+	c.Assert(err, check.IsNil)
38
+	assertNwIsAvailable(c, "test")
45 39
 
46 40
 	runCmd = exec.Command(dockerBinary, "network", "rm", "test")
47
-	out, _, _, err = runCommandWithStdoutStderr(runCmd)
48
-	if err != nil {
49
-		c.Fatal(out, err)
50
-	}
51
-	if isNetworkPresent(c, "test") {
52
-		c.Fatalf("Network test is not removed")
53
-	}
41
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
42
+	c.Assert(err, check.IsNil)
43
+	assertNwNotAvailable(c, "test")
54 44
 }
... ...
@@ -3,18 +3,29 @@
3 3
 package main
4 4
 
5 5
 import (
6
+	"fmt"
6 7
 	"os/exec"
7 8
 	"strings"
8 9
 
9 10
 	"github.com/go-check/check"
10 11
 )
11 12
 
12
-func isSrvAvailable(c *check.C, sname string, name string) bool {
13
+func assertSrvIsAvailable(c *check.C, sname, name string) {
14
+	if !isSrvPresent(c, sname, name) {
15
+		c.Fatalf("Service %s on network %s not found in service ls o/p", sname, name)
16
+	}
17
+}
18
+
19
+func assertSrvNotAvailable(c *check.C, sname, name string) {
20
+	if isSrvPresent(c, sname, name) {
21
+		c.Fatalf("Found service %s on network %s in service ls o/p", sname, name)
22
+	}
23
+}
24
+
25
+func isSrvPresent(c *check.C, sname, name string) bool {
13 26
 	runCmd := exec.Command(dockerBinary, "service", "ls")
14 27
 	out, _, _, err := runCommandWithStdoutStderr(runCmd)
15
-	if err != nil {
16
-		c.Fatal(out, err)
17
-	}
28
+	c.Assert(err, check.IsNil)
18 29
 	lines := strings.Split(out, "\n")
19 30
 	for i := 1; i < len(lines)-1; i++ {
20 31
 		if strings.Contains(lines[i], sname) && strings.Contains(lines[i], name) {
... ...
@@ -23,15 +34,15 @@ func isSrvAvailable(c *check.C, sname string, name string) bool {
23 23
 	}
24 24
 	return false
25 25
 }
26
-func isNwAvailable(c *check.C, name string) bool {
27
-	runCmd := exec.Command(dockerBinary, "network", "ls")
26
+
27
+func isCntPresent(c *check.C, cname, sname, name string) bool {
28
+	runCmd := exec.Command(dockerBinary, "service", "ls", "--no-trunc")
28 29
 	out, _, _, err := runCommandWithStdoutStderr(runCmd)
29
-	if err != nil {
30
-		c.Fatal(out, err)
31
-	}
30
+	c.Assert(err, check.IsNil)
32 31
 	lines := strings.Split(out, "\n")
33 32
 	for i := 1; i < len(lines)-1; i++ {
34
-		if strings.Contains(lines[i], name) {
33
+		fmt.Println(lines)
34
+		if strings.Contains(lines[i], name) && strings.Contains(lines[i], sname) && strings.Contains(lines[i], cname) {
35 35
 			return true
36 36
 		}
37 37
 	}
... ...
@@ -40,38 +51,36 @@ func isNwAvailable(c *check.C, name string) bool {
40 40
 
41 41
 func (s *DockerSuite) TestDockerServiceCreateDelete(c *check.C) {
42 42
 	runCmd := exec.Command(dockerBinary, "network", "create", "test")
43
-	out, _, _, err := runCommandWithStdoutStderr(runCmd)
44
-	if err != nil {
45
-		c.Fatal(out, err)
46
-	}
47
-	if !isNwAvailable(c, "test") {
48
-		c.Fatalf("Network test not found")
49
-	}
43
+	_, _, _, err := runCommandWithStdoutStderr(runCmd)
44
+	c.Assert(err, check.IsNil)
45
+	assertNwIsAvailable(c, "test")
50 46
 
51 47
 	runCmd = exec.Command(dockerBinary, "service", "publish", "s1.test")
52
-	out, _, _, err = runCommandWithStdoutStderr(runCmd)
53
-	if err != nil {
54
-		c.Fatal(out, err)
55
-	}
56
-	if !isSrvAvailable(c, "s1", "test") {
57
-		c.Fatalf("service s1.test not found")
58
-	}
48
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
49
+	c.Assert(err, check.IsNil)
50
+	assertSrvIsAvailable(c, "s1", "test")
59 51
 
60 52
 	runCmd = exec.Command(dockerBinary, "service", "unpublish", "s1.test")
61
-	out, _, _, err = runCommandWithStdoutStderr(runCmd)
62
-	if err != nil {
63
-		c.Fatal(out, err)
64
-	}
65
-	if isSrvAvailable(c, "s1", "test") {
66
-		c.Fatalf("service s1.test not removed")
67
-	}
53
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
54
+	c.Assert(err, check.IsNil)
55
+	assertSrvNotAvailable(c, "s1", "test")
68 56
 
69 57
 	runCmd = exec.Command(dockerBinary, "network", "rm", "test")
70
-	out, _, _, err = runCommandWithStdoutStderr(runCmd)
71
-	if err != nil {
72
-		c.Fatal(out, err)
73
-	}
74
-	if isNetworkPresent(c, "test") {
75
-		c.Fatalf("Network test is not removed")
76
-	}
58
+	_, _, _, err = runCommandWithStdoutStderr(runCmd)
59
+	c.Assert(err, check.IsNil)
60
+	assertNwNotAvailable(c, "test")
61
+}
62
+
63
+func (s *DockerSuite) TestDockerPublishServiceFlag(c *check.C) {
64
+	// Run saying the container is the backend for the specified service on the specified network
65
+	runCmd := exec.Command(dockerBinary, "run", "-d", "--expose=23", "--publish-service", "telnet.production", "busybox", "top")
66
+	out, _, err := runCommandWithOutput(runCmd)
67
+	c.Assert(err, check.IsNil)
68
+	cid := strings.TrimSpace(out)
69
+
70
+	// Verify container is attached in service ps o/p
71
+	assertSrvIsAvailable(c, "telnet", "production")
72
+	runCmd = exec.Command(dockerBinary, "rm", "-f", cid)
73
+	out, _, err = runCommandWithOutput(runCmd)
74
+	c.Assert(err, check.IsNil)
77 75
 }
... ...
@@ -114,6 +114,7 @@ type Config struct {
114 114
 	AttachStdout    bool
115 115
 	AttachStderr    bool
116 116
 	ExposedPorts    map[nat.Port]struct{}
117
+	PublishService  string
117 118
 	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
118 119
 	OpenStdin       bool // Open stdin
119 120
 	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
... ...
@@ -11,9 +11,11 @@ type experimentalFlags struct {
11 11
 func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
12 12
 	flags := make(map[string]interface{})
13 13
 	flags["volume-driver"] = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
14
+	flags["publish-service"] = cmd.String([]string{"-publish-service"}, "", "Publish this container as a service")
14 15
 	return &experimentalFlags{flags: flags}
15 16
 }
16 17
 
17 18
 func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) {
18 19
 	config.VolumeDriver = *(exp.flags["volume-driver"]).(*string)
20
+	config.PublishService = *(exp.flags["publish-service"]).(*string)
19 21
 }