From 3e7db73b99498d037b43eb59972a6360cfbc971b Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Sun, 25 Oct 2015 17:12:22 -0700 Subject: [PATCH] Enhancing --cluster-advertise to support --cluster-advertise daemon option is enahanced to support in addition to in order to amke it automation friendly using docker-machine. Signed-off-by: Madhu Venugopal --- api/client/info.go | 3 ++ api/types/types.go | 1 + daemon/config.go | 2 +- daemon/daemon.go | 11 ++++- daemon/info.go | 1 + integration-cli/docker_cli_info_test.go | 49 ++++++++++++++++++++- pkg/discovery/backends.go | 58 +++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 4 deletions(-) diff --git a/api/client/info.go b/api/client/info.go index 06171826a6..ebed2452e2 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -107,5 +107,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore) } + if info.ClusterAdvertise != "" { + fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise) + } return nil } diff --git a/api/types/types.go b/api/types/types.go index efe5ea28be..dc6278087d 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -219,6 +219,7 @@ type Info struct { ExperimentalBuild bool ServerVersion string ClusterStore string + ClusterAdvertise string } // ExecStartCheck is a temp struct used by execStart diff --git a/daemon/config.go b/daemon/config.go index d335b6557b..436f8cdac4 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -71,7 +71,7 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) cmd.Var(opts.NewListOptsRef(&config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon")) cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs")) cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options")) - cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address of the daemon instance to advertise")) + cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise")) cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store")) cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options")) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 83b15a302e..61a5b04905 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -767,10 +767,17 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo // initialized, the daemon is registered and we can store the discovery backend as its read-only // DiscoveryWatcher version. if config.ClusterStore != "" && config.ClusterAdvertise != "" { - var err error - if d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts); err != nil { + advertise, err := discovery.ParseAdvertise(config.ClusterStore, config.ClusterAdvertise) + if err != nil { + return nil, fmt.Errorf("discovery advertise parsing failed (%v)", err) + } + config.ClusterAdvertise = advertise + d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts) + if err != nil { return nil, fmt.Errorf("discovery initialization failed (%v)", err) } + } else if config.ClusterAdvertise != "" { + return nil, fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration") } d.netController, err = d.initNetworkController(config) diff --git a/daemon/info.go b/daemon/info.go index 395c34d409..f977f69700 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -92,6 +92,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { ExperimentalBuild: utils.ExperimentalBuild(), ServerVersion: dockerversion.VERSION, ClusterStore: daemon.config().ClusterStore, + ClusterAdvertise: daemon.config().ClusterAdvertise, } // TODO Windows. Refactor this more once sysinfo is refactored into diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index 9a61449ced..21b8e51682 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "net" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/utils" @@ -42,11 +43,57 @@ func (s *DockerSuite) TestInfoDiscoveryBackend(c *check.C) { d := NewDaemon(c) discoveryBackend := "consul://consuladdr:consulport/some/path" - err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), "--cluster-advertise=foo") + discoveryAdvertise := "1.1.1.1:2375" + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), fmt.Sprintf("--cluster-advertise=%s", discoveryAdvertise)) c.Assert(err, checker.IsNil) defer d.Stop() out, err := d.Cmd("info") c.Assert(err, checker.IsNil) c.Assert(out, checker.Contains, fmt.Sprintf("Cluster store: %s\n", discoveryBackend)) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster advertise: %s\n", discoveryAdvertise)) +} + +// TestInfoDiscoveryInvalidAdvertise verifies that a daemon run with +// an invalid `--cluster-advertise` configuration +func (s *DockerSuite) TestInfoDiscoveryInvalidAdvertise(c *check.C) { + testRequires(c, SameHostDaemon) + + d := NewDaemon(c) + discoveryBackend := "consul://consuladdr:consulport/some/path" + + // --cluster-advertise with an invalid string is an error + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), "--cluster-advertise=invalid") + c.Assert(err, checker.Not(checker.IsNil)) + + // --cluster-advertise without --cluster-store is also an error + err = d.Start("--cluster-advertise=1.1.1.1:2375") + c.Assert(err, checker.Not(checker.IsNil)) +} + +// TestInfoDiscoveryAdvertiseInterfaceName verifies that a daemon run with `--cluster-advertise` +// configured with interface name properly show the advertise ip-address in info output. +func (s *DockerSuite) TestInfoDiscoveryAdvertiseInterfaceName(c *check.C) { + testRequires(c, SameHostDaemon) + + d := NewDaemon(c) + discoveryBackend := "consul://consuladdr:consulport/some/path" + discoveryAdvertise := "eth0" + + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), fmt.Sprintf("--cluster-advertise=%s:2375", discoveryAdvertise)) + c.Assert(err, checker.IsNil) + defer d.Stop() + + iface, err := net.InterfaceByName(discoveryAdvertise) + c.Assert(err, checker.IsNil) + addrs, err := iface.Addrs() + c.Assert(err, checker.IsNil) + c.Assert(len(addrs), checker.GreaterThan, 0) + ip, _, err := net.ParseCIDR(addrs[0].String()) + c.Assert(err, checker.IsNil) + + out, err := d.Cmd("info") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster store: %s\n", discoveryBackend)) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster advertise: %s:2375\n", ip.String())) } diff --git a/pkg/discovery/backends.go b/pkg/discovery/backends.go index 3874a7d04e..875a26c442 100644 --- a/pkg/discovery/backends.go +++ b/pkg/discovery/backends.go @@ -2,6 +2,7 @@ package discovery import ( "fmt" + "net" "strings" "time" @@ -39,6 +40,63 @@ func parse(rawurl string) (string, string) { return parts[0], parts[1] } +// ParseAdvertise parses the --cluster-advertise daemon config which accepts +// : or : +func ParseAdvertise(store, advertise string) (string, error) { + var ( + iface *net.Interface + addrs []net.Addr + err error + ) + + addr, port, err := net.SplitHostPort(advertise) + + if err != nil { + return "", fmt.Errorf("invalid --cluster-advertise configuration: %s: %v", advertise, err) + } + + ip := net.ParseIP(addr) + // If it is a valid ip-address, use it as is + if ip != nil { + return advertise, nil + } + + // If advertise is a valid interface name, get the valid ipv4 address and use it to advertise + ifaceName := addr + iface, err = net.InterfaceByName(ifaceName) + if err != nil { + return "", fmt.Errorf("invalid cluster advertise IP address or interface name (%s) : %v", advertise, err) + } + + addrs, err = iface.Addrs() + if err != nil { + return "", fmt.Errorf("unable to get advertise IP address from interface (%s) : %v", advertise, err) + } + + if addrs == nil || len(addrs) == 0 { + return "", fmt.Errorf("no available advertise IP address in interface (%s)", advertise) + } + + addr = "" + for _, a := range addrs { + ip, _, err := net.ParseCIDR(a.String()) + if err != nil { + return "", fmt.Errorf("error deriving advertise ip-address in interface (%s) : %v", advertise, err) + } + if ip.To4() == nil || ip.IsLoopback() { + continue + } + addr = ip.String() + break + } + if addr == "" { + return "", fmt.Errorf("couldnt find a valid ip-address in interface %s", advertise) + } + + addr = fmt.Sprintf("%s:%s", addr, port) + return addr, nil +} + // New returns a new Discovery given a URL, heartbeat and ttl settings. // Returns an error if the URL scheme is not supported. func New(rawurl string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) (Backend, error) {