From 1ac350a0ecfe9d28c82d6dff6a763a378ff9c472 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Sun, 14 Jun 2015 22:31:09 -0700 Subject: [PATCH] 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 Signed-off-by: Madhu Venugopal --- daemon/container_linux.go | 60 +++++++++++---- integration-cli/docker_cli_network_test.go | 42 +++++------ integration-cli/docker_cli_service_test.go | 85 ++++++++++++---------- runconfig/config.go | 1 + runconfig/parse_experimental.go | 2 + 5 files changed, 118 insertions(+), 72 deletions(-) diff --git a/daemon/container_linux.go b/daemon/container_linux.go index 9c8945499e..e8f918a048 100644 --- a/daemon/container_linux.go +++ b/daemon/container_linux.go @@ -737,11 +737,23 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO 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{} genericOption := options.Generic{} - dnet := controller.Config().Daemon.DefaultNetwork - driver := controller.Config().Daemon.DefaultDriver // Bridge driver is special due to legacy reasons if runconfig.NetworkMode(driver).IsBridge() { @@ -763,31 +775,53 @@ func (container *Container) AllocateNetwork() error { return nil } + var networkDriver string + service := container.Config.PublishService networkName := mode.NetworkName() 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 n, err := controller.NetworkByName(networkName) 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 } } - createOptions, err := container.buildCreateEndpointOptions() + ep, err := n.EndpointByName(service) 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 { diff --git a/integration-cli/docker_cli_network_test.go b/integration-cli/docker_cli_network_test.go index 820a0ae8a9..a79d65a92e 100644 --- a/integration-cli/docker_cli_network_test.go +++ b/integration-cli/docker_cli_network_test.go @@ -9,12 +9,22 @@ import ( "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") out, _, _, err := runCommandWithStdoutStderr(runCmd) - if err != nil { - c.Fatal(out, err) - } + c.Assert(err, check.IsNil) lines := strings.Split(out, "\n") for i := 1; i < len(lines)-1; i++ { 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) { defaults := []string{"bridge", "host", "none"} 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) { 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") - 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") } diff --git a/integration-cli/docker_cli_service_test.go b/integration-cli/docker_cli_service_test.go index 5b618b278b..d61871e1bd 100644 --- a/integration-cli/docker_cli_service_test.go +++ b/integration-cli/docker_cli_service_test.go @@ -3,18 +3,29 @@ package main import ( + "fmt" "os/exec" "strings" "github.com/go-check/check" ) -func isSrvAvailable(c *check.C, sname string, name string) bool { +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) - if err != nil { - c.Fatal(out, err) - } + 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) { @@ -23,15 +34,15 @@ func isSrvAvailable(c *check.C, sname string, name string) bool { } return false } -func isNwAvailable(c *check.C, name string) bool { - runCmd := exec.Command(dockerBinary, "network", "ls") + +func isCntPresent(c *check.C, cname, sname, name string) bool { + runCmd := exec.Command(dockerBinary, "service", "ls", "--no-trunc") out, _, _, err := runCommandWithStdoutStderr(runCmd) - if err != nil { - c.Fatal(out, err) - } + c.Assert(err, check.IsNil) lines := strings.Split(out, "\n") for i := 1; i < len(lines)-1; i++ { - if strings.Contains(lines[i], name) { + fmt.Println(lines) + if strings.Contains(lines[i], name) && strings.Contains(lines[i], sname) && strings.Contains(lines[i], cname) { return true } } @@ -40,38 +51,36 @@ func isNwAvailable(c *check.C, name string) bool { func (s *DockerSuite) TestDockerServiceCreateDelete(c *check.C) { runCmd := exec.Command(dockerBinary, "network", "create", "test") - out, _, _, err := runCommandWithStdoutStderr(runCmd) - if err != nil { - c.Fatal(out, err) - } - if !isNwAvailable(c, "test") { - c.Fatalf("Network test not found") - } + _, _, _, err := runCommandWithStdoutStderr(runCmd) + c.Assert(err, check.IsNil) + assertNwIsAvailable(c, "test") runCmd = exec.Command(dockerBinary, "service", "publish", "s1.test") - out, _, _, err = runCommandWithStdoutStderr(runCmd) - if err != nil { - c.Fatal(out, err) - } - if !isSrvAvailable(c, "s1", "test") { - c.Fatalf("service s1.test not found") - } + _, _, _, err = runCommandWithStdoutStderr(runCmd) + c.Assert(err, check.IsNil) + assertSrvIsAvailable(c, "s1", "test") runCmd = exec.Command(dockerBinary, "service", "unpublish", "s1.test") - out, _, _, err = runCommandWithStdoutStderr(runCmd) - if err != nil { - c.Fatal(out, err) - } - if isSrvAvailable(c, "s1", "test") { - c.Fatalf("service s1.test not removed") - } + _, _, _, err = runCommandWithStdoutStderr(runCmd) + c.Assert(err, check.IsNil) + assertSrvNotAvailable(c, "s1", "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") +} + +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) } diff --git a/runconfig/config.go b/runconfig/config.go index 8c578ee6cc..786b075618 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -114,6 +114,7 @@ type Config struct { AttachStdout bool AttachStderr bool ExposedPorts map[nat.Port]struct{} + PublishService string Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. diff --git a/runconfig/parse_experimental.go b/runconfig/parse_experimental.go index 886b377fa8..d00e69c5c6 100644 --- a/runconfig/parse_experimental.go +++ b/runconfig/parse_experimental.go @@ -11,9 +11,11 @@ type experimentalFlags struct { func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags { flags := make(map[string]interface{}) 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} } func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) { config.VolumeDriver = *(exp.flags["volume-driver"]).(*string) + config.PublishService = *(exp.flags["publish-service"]).(*string) }