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