draft of fix for issue #447. allows advertising separate host:ports to client.

This commit is contained in:
Peter Miron
2017-11-27 15:34:15 -05:00
parent cde2aa6d2f
commit 6852298e7b
3 changed files with 100 additions and 32 deletions

View File

@@ -30,6 +30,7 @@ type ClusterOpts struct {
TLSTimeout float64 `json:"-"`
TLSConfig *tls.Config `json:"-"`
ListenStr string `json:"-"`
AdvertiseStr string `json:"-"`
NoAdvertise bool `json:"-"`
ConnectRetries int `json:"-"`
}
@@ -387,6 +388,8 @@ func parseCluster(cm map[string]interface{}, opts *Options) error {
opts.Cluster.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
opts.Cluster.TLSConfig.RootCAs = opts.Cluster.TLSConfig.ClientCAs
opts.Cluster.TLSTimeout = tc.Timeout
case "cluster_advertise":
opts.Cluster.AdvertiseStr = mv.(string)
case "no_advertise":
opts.Cluster.NoAdvertise = mv.(bool)
case "connect_retries":
@@ -753,6 +756,9 @@ func MergeOptions(fileOpts, flagOpts *Options) *Options {
if flagOpts.Cluster.ListenStr != "" {
opts.Cluster.ListenStr = flagOpts.Cluster.ListenStr
}
if flagOpts.Cluster.AdvertiseStr != "" {
opts.Cluster.AdvertiseStr = flagOpts.Cluster.AdvertiseStr
}
if flagOpts.Cluster.NoAdvertise {
opts.Cluster.NoAdvertise = true
}
@@ -974,6 +980,7 @@ func ConfigureOptions(fs *flag.FlagSet, args []string, printVersion, printHelp,
fs.StringVar(&opts.RoutesStr, "routes", "", "Routes to actively solicit a connection.")
fs.StringVar(&opts.Cluster.ListenStr, "cluster", "", "Cluster url from which members can solicit routes.")
fs.StringVar(&opts.Cluster.ListenStr, "cluster_listen", "", "Cluster url from which members can solicit routes.")
fs.StringVar(&opts.Cluster.AdvertiseStr, "cluster_advertise", "", "Cluster URL sent on info for use with proxied connections.")
fs.BoolVar(&opts.Cluster.NoAdvertise, "no_advertise", false, "Advertise known cluster IPs to clients.")
fs.IntVar(&opts.Cluster.ConnectRetries, "connect_retries", 0, "For implicit routes, number of connect retries")
fs.BoolVar(&showTLSHelp, "help_tls", false, "TLS help.")
@@ -1171,6 +1178,7 @@ func overrideCluster(opts *Options) error {
opts.Cluster.Username = ""
opts.Cluster.Password = ""
}
return nil
}

View File

@@ -1002,42 +1002,62 @@ func (s *Server) getClientConnectURLs() []string {
sPort := strconv.Itoa(opts.Port)
urls := make([]string, 0, 1)
ipAddr, err := net.ResolveIPAddr("ip", opts.Host)
// If the host is "any" (0.0.0.0 or ::), get specific IPs from available
// interfaces.
if err == nil && ipAddr.IP.IsUnspecified() {
var ip net.IP
ifaces, _ := net.Interfaces()
for _, i := range ifaces {
addrs, _ := i.Addrs()
for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
// short circuit if cluster-advertise is set
if opts.Cluster.AdvertiseStr != "" {
hosts := strings.Split(opts.Cluster.AdvertiseStr, ",")
for _, i := range hosts {
hostPort := strings.Split(i, ":")
host := strings.TrimSpace(hostPort[0])
if len(hostPort) > 1 {
port := strings.TrimSpace(hostPort[1])
// if a separate advertise port is set, use that. Otherwise, use the main listen port.
if port != "" {
sPort = port
}
// Skip non global unicast addresses
if !ip.IsGlobalUnicast() || ip.IsUnspecified() {
ip = nil
continue
}
urls = append(urls, net.JoinHostPort(host, sPort))
}
} else {
ipAddr, err := net.ResolveIPAddr("ip", opts.Host)
// If the host is "any" (0.0.0.0 or ::), get specific IPs from available
// interfaces.
if err == nil && ipAddr.IP.IsUnspecified() {
var ip net.IP
ifaces, _ := net.Interfaces()
for _, i := range ifaces {
addrs, _ := i.Addrs()
for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
// Skip non global unicast addresses
if !ip.IsGlobalUnicast() || ip.IsUnspecified() {
ip = nil
continue
}
urls = append(urls, net.JoinHostPort(ip.String(), sPort))
}
urls = append(urls, net.JoinHostPort(ip.String(), sPort))
}
}
if err != nil || len(urls) == 0 {
// We are here if s.opts.Host is not "0.0.0.0" nor "::", or if for some
// reason we could not add any URL in the loop above.
// We had a case where a Windows VM was hosed and would have err == nil
// and not add any address in the array in the loop above, and we
// ended-up returning 0.0.0.0, which is problematic for Windows clients.
// Check for 0.0.0.0 or :: specifically, and ignore if that's the case.
if opts.Host == "0.0.0.0" || opts.Host == "::" {
s.Errorf("Address %q can not be resolved properly", opts.Host)
} else {
urls = append(urls, net.JoinHostPort(opts.Host, sPort))
}
}
}
if err != nil || len(urls) == 0 {
// We are here if s.opts.Host is not "0.0.0.0" nor "::", or if for some
// reason we could not add any URL in the loop above.
// We had a case where a Windows VM was hosed and would have err == nil
// and not add any address in the array in the loop above, and we
// ended-up returning 0.0.0.0, which is problematic for Windows clients.
// Check for 0.0.0.0 or :: specifically, and ignore if that's the case.
if opts.Host == "0.0.0.0" || opts.Host == "::" {
s.Errorf("Address %q can not be resolved properly", opts.Host)
} else {
urls = append(urls, net.JoinHostPort(opts.Host, sPort))
}
}
return urls
}

View File

@@ -213,6 +213,46 @@ func TestGetConnectURLs(t *testing.T) {
}
}
func TestClusterAdvertiseConnectURL(t *testing.T) {
opts := DefaultOptions()
opts.Port = 4222
opts.Cluster.AdvertiseStr = "nats.example.com"
s := New(opts)
urls := s.getClientConnectURLs()
if len(urls) != 1 {
t.Fatalf("Expected to get one url, got none: %v with Cluster.AdvertiseStr %v",
opts.Host, opts.Cluster.AdvertiseStr)
}
if urls[0] != "nats.example.com:4222" {
t.Fatalf("Expected to get '%s', got: '%v'", "nats.example.com:4222", urls[0])
}
s.Shutdown()
opts.Cluster.AdvertiseStr = "nats.example.com, nats2.example.com:7777"
s = New(opts)
urls = s.getClientConnectURLs()
if len(urls) != 2 {
t.Fatalf("Expected to get two urls, got %d: %v", len(urls), opts.Cluster.AdvertiseStr)
}
if urls[0] != "nats.example.com:4222" {
t.Fatalf("Expected 'nats.example.com:4222', got: '%v'", urls[0])
}
if urls[1] != "nats2.example.com:7777" {
t.Fatalf("Expected 'nats2.example.com:7777', got: '%v'", urls[1])
}
s.Shutdown()
}
func TestNoDeadlockOnStartFailure(t *testing.T) {
opts := DefaultOptions()
opts.Host = "x.x.x.x" // bad host