Browse code

Enhancing --cluster-advertise to support <interface-name>

--cluster-advertise daemon option is enahanced to support <interface-name>
in addition to <ip-address> in order to amke it automation friendly using
docker-machine.

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

Madhu Venugopal authored on 2015/10/26 09:12:22
Showing 7 changed files
... ...
@@ -107,5 +107,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
107 107
 		fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
108 108
 	}
109 109
 
110
+	if info.ClusterAdvertise != "" {
111
+		fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise)
112
+	}
110 113
 	return nil
111 114
 }
... ...
@@ -219,6 +219,7 @@ type Info struct {
219 219
 	ExperimentalBuild  bool
220 220
 	ServerVersion      string
221 221
 	ClusterStore       string
222
+	ClusterAdvertise   string
222 223
 }
223 224
 
224 225
 // ExecStartCheck is a temp struct used by execStart
... ...
@@ -71,7 +71,7 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string)
71 71
 	cmd.Var(opts.NewListOptsRef(&config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
72 72
 	cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
73 73
 	cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
74
-	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address of the daemon instance to advertise"))
74
+	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
75 75
 	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
76 76
 	cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
77 77
 }
... ...
@@ -767,10 +767,17 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
767 767
 	// initialized, the daemon is registered and we can store the discovery backend as its read-only
768 768
 	// DiscoveryWatcher version.
769 769
 	if config.ClusterStore != "" && config.ClusterAdvertise != "" {
770
-		var err error
771
-		if d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts); err != nil {
770
+		advertise, err := discovery.ParseAdvertise(config.ClusterStore, config.ClusterAdvertise)
771
+		if err != nil {
772
+			return nil, fmt.Errorf("discovery advertise parsing failed (%v)", err)
773
+		}
774
+		config.ClusterAdvertise = advertise
775
+		d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
776
+		if err != nil {
772 777
 			return nil, fmt.Errorf("discovery initialization failed (%v)", err)
773 778
 		}
779
+	} else if config.ClusterAdvertise != "" {
780
+		return nil, fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
774 781
 	}
775 782
 
776 783
 	d.netController, err = d.initNetworkController(config)
... ...
@@ -92,6 +92,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
92 92
 		ExperimentalBuild:  utils.ExperimentalBuild(),
93 93
 		ServerVersion:      dockerversion.VERSION,
94 94
 		ClusterStore:       daemon.config().ClusterStore,
95
+		ClusterAdvertise:   daemon.config().ClusterAdvertise,
95 96
 	}
96 97
 
97 98
 	// TODO Windows. Refactor this more once sysinfo is refactored into
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"net"
5 6
 
6 7
 	"github.com/docker/docker/pkg/integration/checker"
7 8
 	"github.com/docker/docker/utils"
... ...
@@ -42,11 +43,57 @@ func (s *DockerSuite) TestInfoDiscoveryBackend(c *check.C) {
42 42
 
43 43
 	d := NewDaemon(c)
44 44
 	discoveryBackend := "consul://consuladdr:consulport/some/path"
45
-	err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), "--cluster-advertise=foo")
45
+	discoveryAdvertise := "1.1.1.1:2375"
46
+	err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), fmt.Sprintf("--cluster-advertise=%s", discoveryAdvertise))
46 47
 	c.Assert(err, checker.IsNil)
47 48
 	defer d.Stop()
48 49
 
49 50
 	out, err := d.Cmd("info")
50 51
 	c.Assert(err, checker.IsNil)
51 52
 	c.Assert(out, checker.Contains, fmt.Sprintf("Cluster store: %s\n", discoveryBackend))
53
+	c.Assert(out, checker.Contains, fmt.Sprintf("Cluster advertise: %s\n", discoveryAdvertise))
54
+}
55
+
56
+// TestInfoDiscoveryInvalidAdvertise verifies that a daemon run with
57
+// an invalid `--cluster-advertise` configuration
58
+func (s *DockerSuite) TestInfoDiscoveryInvalidAdvertise(c *check.C) {
59
+	testRequires(c, SameHostDaemon)
60
+
61
+	d := NewDaemon(c)
62
+	discoveryBackend := "consul://consuladdr:consulport/some/path"
63
+
64
+	// --cluster-advertise with an invalid string is an error
65
+	err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), "--cluster-advertise=invalid")
66
+	c.Assert(err, checker.Not(checker.IsNil))
67
+
68
+	// --cluster-advertise without --cluster-store is also an error
69
+	err = d.Start("--cluster-advertise=1.1.1.1:2375")
70
+	c.Assert(err, checker.Not(checker.IsNil))
71
+}
72
+
73
+// TestInfoDiscoveryAdvertiseInterfaceName verifies that a daemon run with `--cluster-advertise`
74
+// configured with interface name properly show the advertise ip-address in info output.
75
+func (s *DockerSuite) TestInfoDiscoveryAdvertiseInterfaceName(c *check.C) {
76
+	testRequires(c, SameHostDaemon)
77
+
78
+	d := NewDaemon(c)
79
+	discoveryBackend := "consul://consuladdr:consulport/some/path"
80
+	discoveryAdvertise := "eth0"
81
+
82
+	err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), fmt.Sprintf("--cluster-advertise=%s:2375", discoveryAdvertise))
83
+	c.Assert(err, checker.IsNil)
84
+	defer d.Stop()
85
+
86
+	iface, err := net.InterfaceByName(discoveryAdvertise)
87
+	c.Assert(err, checker.IsNil)
88
+	addrs, err := iface.Addrs()
89
+	c.Assert(err, checker.IsNil)
90
+	c.Assert(len(addrs), checker.GreaterThan, 0)
91
+	ip, _, err := net.ParseCIDR(addrs[0].String())
92
+	c.Assert(err, checker.IsNil)
93
+
94
+	out, err := d.Cmd("info")
95
+	c.Assert(err, checker.IsNil)
96
+	c.Assert(out, checker.Contains, fmt.Sprintf("Cluster store: %s\n", discoveryBackend))
97
+	c.Assert(out, checker.Contains, fmt.Sprintf("Cluster advertise: %s:2375\n", ip.String()))
52 98
 }
... ...
@@ -2,6 +2,7 @@ package discovery
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"net"
5 6
 	"strings"
6 7
 	"time"
7 8
 
... ...
@@ -39,6 +40,63 @@ func parse(rawurl string) (string, string) {
39 39
 	return parts[0], parts[1]
40 40
 }
41 41
 
42
+// ParseAdvertise parses the --cluster-advertise daemon config which accepts
43
+// <ip-address>:<port> or <interface-name>:<port>
44
+func ParseAdvertise(store, advertise string) (string, error) {
45
+	var (
46
+		iface *net.Interface
47
+		addrs []net.Addr
48
+		err   error
49
+	)
50
+
51
+	addr, port, err := net.SplitHostPort(advertise)
52
+
53
+	if err != nil {
54
+		return "", fmt.Errorf("invalid --cluster-advertise configuration: %s: %v", advertise, err)
55
+	}
56
+
57
+	ip := net.ParseIP(addr)
58
+	// If it is a valid ip-address, use it as is
59
+	if ip != nil {
60
+		return advertise, nil
61
+	}
62
+
63
+	// If advertise is a valid interface name, get the valid ipv4 address and use it to advertise
64
+	ifaceName := addr
65
+	iface, err = net.InterfaceByName(ifaceName)
66
+	if err != nil {
67
+		return "", fmt.Errorf("invalid cluster advertise IP address or interface name (%s) : %v", advertise, err)
68
+	}
69
+
70
+	addrs, err = iface.Addrs()
71
+	if err != nil {
72
+		return "", fmt.Errorf("unable to get advertise IP address from interface (%s) : %v", advertise, err)
73
+	}
74
+
75
+	if addrs == nil || len(addrs) == 0 {
76
+		return "", fmt.Errorf("no available advertise IP address in interface (%s)", advertise)
77
+	}
78
+
79
+	addr = ""
80
+	for _, a := range addrs {
81
+		ip, _, err := net.ParseCIDR(a.String())
82
+		if err != nil {
83
+			return "", fmt.Errorf("error deriving advertise ip-address in interface (%s) : %v", advertise, err)
84
+		}
85
+		if ip.To4() == nil || ip.IsLoopback() {
86
+			continue
87
+		}
88
+		addr = ip.String()
89
+		break
90
+	}
91
+	if addr == "" {
92
+		return "", fmt.Errorf("couldnt find a valid ip-address in interface %s", advertise)
93
+	}
94
+
95
+	addr = fmt.Sprintf("%s:%s", addr, port)
96
+	return addr, nil
97
+}
98
+
42 99
 // New returns a new Discovery given a URL, heartbeat and ttl settings.
43 100
 // Returns an error if the URL scheme is not supported.
44 101
 func New(rawurl string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) (Backend, error) {