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>
| ... | ... |
@@ -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 |
} |