diff --git a/server/const.go b/server/const.go index 270ed1a0..302eb991 100644 --- a/server/const.go +++ b/server/const.go @@ -13,6 +13,11 @@ const ( // DEFAULT_PORT is the deault port for client connections. DEFAULT_PORT = 4222 + // RANDOM_PORT is the value for port that, when supplied, will cause the + // server to listen on a randomly-chosen available port. The resolved port + // is available via the Addr() method. + RANDOM_PORT = -1 + // DEFAULT_HOST defaults to all interfaces. DEFAULT_HOST = "0.0.0.0" diff --git a/server/opts.go b/server/opts.go index b7f0787d..5aee72fc 100644 --- a/server/opts.go +++ b/server/opts.go @@ -213,6 +213,9 @@ func processOptions(opts *Options) { } if opts.Port == 0 { opts.Port = DEFAULT_PORT + } else if opts.Port == RANDOM_PORT { + // Choose randomly inside of net.Listen + opts.Port = 0 } if opts.MaxConn == 0 { opts.MaxConn = DEFAULT_MAX_CONNECTIONS diff --git a/server/opts_test.go b/server/opts_test.go index f495136f..c7e11252 100644 --- a/server/opts_test.go +++ b/server/opts_test.go @@ -31,6 +31,16 @@ func TestDefaultOptions(t *testing.T) { } } +func TestOptions_RandomPort(t *testing.T) { + opts := &Options{Port: RANDOM_PORT} + processOptions(opts) + + if opts.Port != 0 { + t.Fatalf("Process of options should have resolved random port to "+ + "zero.\nexpected: %d\ngot: %d\n", 0, opts.Port) + } +} + func TestConfigFile(t *testing.T) { golden := &Options{ Host: "apcera.me", diff --git a/server/server.go b/server/server.go index 6bbbf5e9..b447a1c9 100644 --- a/server/server.go +++ b/server/server.go @@ -274,6 +274,19 @@ func (s *Server) AcceptLoop() { s.listener = l s.mu.Unlock() + // Write resolved port back to options. + _, port, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e) + return + } + portNum, err := strconv.Atoi(port) + if err != nil { + Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e) + return + } + s.opts.Port = portNum + tmpDelay := ACCEPT_MIN_SLEEP for s.isRunning() { diff --git a/test/port_test.go b/test/port_test.go new file mode 100644 index 00000000..b478f4ee --- /dev/null +++ b/test/port_test.go @@ -0,0 +1,41 @@ +// Copyright 2014 Apcera Inc. All rights reserved. + +package test + +import ( + "net" + "strconv" + "testing" + + "github.com/apcera/gnatsd/server" +) + +func TestResolveRandomPort(t *testing.T) { + opts := &server.Options{Port: server.RANDOM_PORT} + s := RunServer(opts) + defer s.Shutdown() + + addr := s.Addr() + _, port, err := net.SplitHostPort(addr.String()) + if err != nil { + t.Fatalf("Expected no error: Got %v\n", err) + } + + portNum, err := strconv.Atoi(port) + if err != nil { + t.Fatalf("Expected no error: Got %v\n", err) + } + + if portNum == server.DEFAULT_PORT { + t.Fatalf("Expected server to choose a random port\nGot: %d", server.DEFAULT_PORT) + } + + if portNum == server.RANDOM_PORT { + t.Fatalf("Expected server to choose a random port\nGot: %d", server.RANDOM_PORT) + } + + if opts.Port != portNum { + t.Fatalf("Options port (%d) should have been overridden by chosen random port (%d)", + opts.Port, portNum) + } +} diff --git a/test/test.go b/test/test.go index eccb5d8d..3c814b94 100644 --- a/test/test.go +++ b/test/test.go @@ -56,10 +56,16 @@ func RunServer(opts *server.Options) *server.Server { go s.Start() // Make sure we are running and can bind before returning. - addr := fmt.Sprintf("%s:%d", opts.Host, opts.Port) end := time.Now().Add(10 * time.Second) for time.Now().Before(end) { - conn, err := net.Dial("tcp", addr) + addr := s.Addr() + if addr == nil { + time.Sleep(10 * time.Millisecond) + // Retry. We might take a little while to open a connection. + continue + } + + conn, err := net.Dial("tcp", addr.String()) if err != nil { time.Sleep(50 * time.Millisecond) // Retry