github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/command/agent/config_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/nomad/nomad/structs/config"
    15  )
    16  
    17  var (
    18  	// trueValue/falseValue are used to get a pointer to a boolean
    19  	trueValue  = true
    20  	falseValue = false
    21  )
    22  
    23  func TestConfig_Merge(t *testing.T) {
    24  	c0 := &Config{}
    25  
    26  	c1 := &Config{
    27  		Telemetry:      &Telemetry{},
    28  		Client:         &ClientConfig{},
    29  		Server:         &ServerConfig{},
    30  		Ports:          &Ports{},
    31  		Addresses:      &Addresses{},
    32  		AdvertiseAddrs: &AdvertiseAddrs{},
    33  		Atlas:          &AtlasConfig{},
    34  		Vault:          &config.VaultConfig{},
    35  		Consul:         &config.ConsulConfig{},
    36  	}
    37  
    38  	c2 := &Config{
    39  		Region:                    "global",
    40  		Datacenter:                "dc1",
    41  		NodeName:                  "node1",
    42  		DataDir:                   "/tmp/dir1",
    43  		LogLevel:                  "INFO",
    44  		EnableDebug:               false,
    45  		LeaveOnInt:                false,
    46  		LeaveOnTerm:               false,
    47  		EnableSyslog:              false,
    48  		SyslogFacility:            "local0.info",
    49  		DisableUpdateCheck:        false,
    50  		DisableAnonymousSignature: false,
    51  		BindAddr:                  "127.0.0.1",
    52  		Telemetry: &Telemetry{
    53  			StatsiteAddr:                       "127.0.0.1:8125",
    54  			StatsdAddr:                         "127.0.0.1:8125",
    55  			DataDogAddr:                        "127.0.0.1:8125",
    56  			DisableHostname:                    false,
    57  			CirconusAPIToken:                   "0",
    58  			CirconusAPIApp:                     "nomadic",
    59  			CirconusAPIURL:                     "http://api.circonus.com/v2",
    60  			CirconusSubmissionInterval:         "60s",
    61  			CirconusCheckSubmissionURL:         "https://someplace.com/metrics",
    62  			CirconusCheckID:                    "0",
    63  			CirconusCheckForceMetricActivation: "true",
    64  			CirconusCheckInstanceID:            "node1:nomadic",
    65  			CirconusCheckSearchTag:             "service:nomadic",
    66  			CirconusCheckDisplayName:           "node1:nomadic",
    67  			CirconusCheckTags:                  "cat1:tag1,cat2:tag2",
    68  			CirconusBrokerID:                   "0",
    69  			CirconusBrokerSelectTag:            "dc:dc1",
    70  		},
    71  		Client: &ClientConfig{
    72  			Enabled:   false,
    73  			StateDir:  "/tmp/state1",
    74  			AllocDir:  "/tmp/alloc1",
    75  			NodeClass: "class1",
    76  			Options: map[string]string{
    77  				"foo": "bar",
    78  			},
    79  			NetworkSpeed:   100,
    80  			CpuCompute:     100,
    81  			MaxKillTimeout: "20s",
    82  			ClientMaxPort:  19996,
    83  			Reserved: &Resources{
    84  				CPU:                 10,
    85  				MemoryMB:            10,
    86  				DiskMB:              10,
    87  				IOPS:                10,
    88  				ReservedPorts:       "1,10-30,55",
    89  				ParsedReservedPorts: []int{1, 2, 4},
    90  			},
    91  		},
    92  		Server: &ServerConfig{
    93  			Enabled:                false,
    94  			BootstrapExpect:        1,
    95  			DataDir:                "/tmp/data1",
    96  			ProtocolVersion:        1,
    97  			NumSchedulers:          1,
    98  			NodeGCThreshold:        "1h",
    99  			HeartbeatGrace:         30 * time.Second,
   100  			MinHeartbeatTTL:        30 * time.Second,
   101  			MaxHeartbeatsPerSecond: 30.0,
   102  		},
   103  		Ports: &Ports{
   104  			HTTP: 4646,
   105  			RPC:  4647,
   106  			Serf: 4648,
   107  		},
   108  		Addresses: &Addresses{
   109  			HTTP: "127.0.0.1",
   110  			RPC:  "127.0.0.1",
   111  			Serf: "127.0.0.1",
   112  		},
   113  		AdvertiseAddrs: &AdvertiseAddrs{
   114  			RPC:  "127.0.0.1",
   115  			Serf: "127.0.0.1",
   116  		},
   117  		Atlas: &AtlasConfig{
   118  			Infrastructure: "hashicorp/test1",
   119  			Token:          "abc",
   120  			Join:           false,
   121  			Endpoint:       "foo",
   122  		},
   123  		HTTPAPIResponseHeaders: map[string]string{
   124  			"Access-Control-Allow-Origin": "*",
   125  		},
   126  		Vault: &config.VaultConfig{
   127  			Token:                "1",
   128  			AllowUnauthenticated: &falseValue,
   129  			TaskTokenTTL:         "1",
   130  			Addr:                 "1",
   131  			TLSCaFile:            "1",
   132  			TLSCaPath:            "1",
   133  			TLSCertFile:          "1",
   134  			TLSKeyFile:           "1",
   135  			TLSSkipVerify:        &falseValue,
   136  			TLSServerName:        "1",
   137  		},
   138  		Consul: &config.ConsulConfig{
   139  			ServerServiceName:  "1",
   140  			ClientServiceName:  "1",
   141  			AutoAdvertise:      &falseValue,
   142  			Addr:               "1",
   143  			Timeout:            1 * time.Second,
   144  			Token:              "1",
   145  			Auth:               "1",
   146  			EnableSSL:          &falseValue,
   147  			VerifySSL:          &falseValue,
   148  			CAFile:             "1",
   149  			CertFile:           "1",
   150  			KeyFile:            "1",
   151  			ServerAutoJoin:     &falseValue,
   152  			ClientAutoJoin:     &falseValue,
   153  			ChecksUseAdvertise: &falseValue,
   154  		},
   155  	}
   156  
   157  	c3 := &Config{
   158  		Region:                    "region2",
   159  		Datacenter:                "dc2",
   160  		NodeName:                  "node2",
   161  		DataDir:                   "/tmp/dir2",
   162  		LogLevel:                  "DEBUG",
   163  		EnableDebug:               true,
   164  		LeaveOnInt:                true,
   165  		LeaveOnTerm:               true,
   166  		EnableSyslog:              true,
   167  		SyslogFacility:            "local0.debug",
   168  		DisableUpdateCheck:        true,
   169  		DisableAnonymousSignature: true,
   170  		BindAddr:                  "127.0.0.2",
   171  		Telemetry: &Telemetry{
   172  			StatsiteAddr:                       "127.0.0.2:8125",
   173  			StatsdAddr:                         "127.0.0.2:8125",
   174  			DataDogAddr:                        "127.0.0.1:8125",
   175  			DisableHostname:                    true,
   176  			PublishNodeMetrics:                 true,
   177  			PublishAllocationMetrics:           true,
   178  			CirconusAPIToken:                   "1",
   179  			CirconusAPIApp:                     "nomad",
   180  			CirconusAPIURL:                     "https://api.circonus.com/v2",
   181  			CirconusSubmissionInterval:         "10s",
   182  			CirconusCheckSubmissionURL:         "https://example.com/metrics",
   183  			CirconusCheckID:                    "1",
   184  			CirconusCheckForceMetricActivation: "false",
   185  			CirconusCheckInstanceID:            "node2:nomad",
   186  			CirconusCheckSearchTag:             "service:nomad",
   187  			CirconusCheckDisplayName:           "node2:nomad",
   188  			CirconusCheckTags:                  "cat1:tag1,cat2:tag2",
   189  			CirconusBrokerID:                   "1",
   190  			CirconusBrokerSelectTag:            "dc:dc2",
   191  		},
   192  		Client: &ClientConfig{
   193  			Enabled:   true,
   194  			StateDir:  "/tmp/state2",
   195  			AllocDir:  "/tmp/alloc2",
   196  			NodeClass: "class2",
   197  			Servers:   []string{"server2"},
   198  			Meta: map[string]string{
   199  				"baz": "zip",
   200  			},
   201  			Options: map[string]string{
   202  				"foo": "bar",
   203  				"baz": "zip",
   204  			},
   205  			ChrootEnv:      map[string]string{},
   206  			ClientMaxPort:  20000,
   207  			ClientMinPort:  22000,
   208  			NetworkSpeed:   105,
   209  			CpuCompute:     105,
   210  			MaxKillTimeout: "50s",
   211  			Reserved: &Resources{
   212  				CPU:                 15,
   213  				MemoryMB:            15,
   214  				DiskMB:              15,
   215  				IOPS:                15,
   216  				ReservedPorts:       "2,10-30,55",
   217  				ParsedReservedPorts: []int{1, 2, 3},
   218  			},
   219  			GCInterval:            6 * time.Second,
   220  			GCParallelDestroys:    6,
   221  			GCDiskUsageThreshold:  71,
   222  			GCInodeUsageThreshold: 86,
   223  		},
   224  		Server: &ServerConfig{
   225  			Enabled:                true,
   226  			BootstrapExpect:        2,
   227  			DataDir:                "/tmp/data2",
   228  			ProtocolVersion:        2,
   229  			NumSchedulers:          2,
   230  			EnabledSchedulers:      []string{structs.JobTypeBatch},
   231  			NodeGCThreshold:        "12h",
   232  			HeartbeatGrace:         2 * time.Minute,
   233  			MinHeartbeatTTL:        2 * time.Minute,
   234  			MaxHeartbeatsPerSecond: 200.0,
   235  			RejoinAfterLeave:       true,
   236  			StartJoin:              []string{"1.1.1.1"},
   237  			RetryJoin:              []string{"1.1.1.1"},
   238  			RetryInterval:          "10s",
   239  			retryInterval:          time.Second * 10,
   240  		},
   241  		Ports: &Ports{
   242  			HTTP: 20000,
   243  			RPC:  21000,
   244  			Serf: 22000,
   245  		},
   246  		Addresses: &Addresses{
   247  			HTTP: "127.0.0.2",
   248  			RPC:  "127.0.0.2",
   249  			Serf: "127.0.0.2",
   250  		},
   251  		AdvertiseAddrs: &AdvertiseAddrs{
   252  			RPC:  "127.0.0.2",
   253  			Serf: "127.0.0.2",
   254  		},
   255  		Atlas: &AtlasConfig{
   256  			Infrastructure: "hashicorp/test2",
   257  			Token:          "xyz",
   258  			Join:           true,
   259  			Endpoint:       "bar",
   260  		},
   261  		HTTPAPIResponseHeaders: map[string]string{
   262  			"Access-Control-Allow-Origin":  "*",
   263  			"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
   264  		},
   265  		Vault: &config.VaultConfig{
   266  			Token:                "2",
   267  			AllowUnauthenticated: &trueValue,
   268  			TaskTokenTTL:         "2",
   269  			Addr:                 "2",
   270  			TLSCaFile:            "2",
   271  			TLSCaPath:            "2",
   272  			TLSCertFile:          "2",
   273  			TLSKeyFile:           "2",
   274  			TLSSkipVerify:        &trueValue,
   275  			TLSServerName:        "2",
   276  		},
   277  		Consul: &config.ConsulConfig{
   278  			ServerServiceName:  "2",
   279  			ClientServiceName:  "2",
   280  			AutoAdvertise:      &trueValue,
   281  			Addr:               "2",
   282  			Timeout:            2 * time.Second,
   283  			Token:              "2",
   284  			Auth:               "2",
   285  			EnableSSL:          &trueValue,
   286  			VerifySSL:          &trueValue,
   287  			CAFile:             "2",
   288  			CertFile:           "2",
   289  			KeyFile:            "2",
   290  			ServerAutoJoin:     &trueValue,
   291  			ClientAutoJoin:     &trueValue,
   292  			ChecksUseAdvertise: &trueValue,
   293  		},
   294  	}
   295  
   296  	result := c0.Merge(c1)
   297  	result = result.Merge(c2)
   298  	result = result.Merge(c3)
   299  	if !reflect.DeepEqual(result, c3) {
   300  		t.Fatalf("bad:\n%#v\n%#v", result, c3)
   301  	}
   302  }
   303  
   304  func TestConfig_ParseConfigFile(t *testing.T) {
   305  	// Fails if the file doesn't exist
   306  	if _, err := ParseConfigFile("/unicorns/leprechauns"); err == nil {
   307  		t.Fatalf("expected error, got nothing")
   308  	}
   309  
   310  	fh, err := ioutil.TempFile("", "nomad")
   311  	if err != nil {
   312  		t.Fatalf("err: %s", err)
   313  	}
   314  	defer os.RemoveAll(fh.Name())
   315  
   316  	// Invalid content returns error
   317  	if _, err := fh.WriteString("nope;!!!"); err != nil {
   318  		t.Fatalf("err: %s", err)
   319  	}
   320  	if _, err := ParseConfigFile(fh.Name()); err == nil {
   321  		t.Fatalf("expected load error, got nothing")
   322  	}
   323  
   324  	// Valid content parses successfully
   325  	if err := fh.Truncate(0); err != nil {
   326  		t.Fatalf("err: %s", err)
   327  	}
   328  	if _, err := fh.Seek(0, 0); err != nil {
   329  		t.Fatalf("err: %s", err)
   330  	}
   331  	if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
   332  		t.Fatalf("err: %s", err)
   333  	}
   334  
   335  	config, err := ParseConfigFile(fh.Name())
   336  	if err != nil {
   337  		t.Fatalf("err: %s", err)
   338  	}
   339  	if config.Region != "west" {
   340  		t.Fatalf("bad region: %q", config.Region)
   341  	}
   342  }
   343  
   344  func TestConfig_LoadConfigDir(t *testing.T) {
   345  	// Fails if the dir doesn't exist.
   346  	if _, err := LoadConfigDir("/unicorns/leprechauns"); err == nil {
   347  		t.Fatalf("expected error, got nothing")
   348  	}
   349  
   350  	dir, err := ioutil.TempDir("", "nomad")
   351  	if err != nil {
   352  		t.Fatalf("err: %s", err)
   353  	}
   354  	defer os.RemoveAll(dir)
   355  
   356  	// Returns empty config on empty dir
   357  	config, err := LoadConfig(dir)
   358  	if err != nil {
   359  		t.Fatalf("err: %s", err)
   360  	}
   361  	if config == nil {
   362  		t.Fatalf("should not be nil")
   363  	}
   364  
   365  	file1 := filepath.Join(dir, "conf1.hcl")
   366  	err = ioutil.WriteFile(file1, []byte(`{"region":"west"}`), 0600)
   367  	if err != nil {
   368  		t.Fatalf("err: %s", err)
   369  	}
   370  
   371  	file2 := filepath.Join(dir, "conf2.hcl")
   372  	err = ioutil.WriteFile(file2, []byte(`{"datacenter":"sfo"}`), 0600)
   373  	if err != nil {
   374  		t.Fatalf("err: %s", err)
   375  	}
   376  
   377  	file3 := filepath.Join(dir, "conf3.hcl")
   378  	err = ioutil.WriteFile(file3, []byte(`nope;!!!`), 0600)
   379  	if err != nil {
   380  		t.Fatalf("err: %s", err)
   381  	}
   382  
   383  	// Fails if we have a bad config file
   384  	if _, err := LoadConfigDir(dir); err == nil {
   385  		t.Fatalf("expected load error, got nothing")
   386  	}
   387  
   388  	if err := os.Remove(file3); err != nil {
   389  		t.Fatalf("err: %s", err)
   390  	}
   391  
   392  	// Works if configs are valid
   393  	config, err = LoadConfigDir(dir)
   394  	if err != nil {
   395  		t.Fatalf("err: %s", err)
   396  	}
   397  	if config.Region != "west" || config.Datacenter != "sfo" {
   398  		t.Fatalf("bad: %#v", config)
   399  	}
   400  }
   401  
   402  func TestConfig_LoadConfig(t *testing.T) {
   403  	// Fails if the target doesn't exist
   404  	if _, err := LoadConfig("/unicorns/leprechauns"); err == nil {
   405  		t.Fatalf("expected error, got nothing")
   406  	}
   407  
   408  	fh, err := ioutil.TempFile("", "nomad")
   409  	if err != nil {
   410  		t.Fatalf("err: %s", err)
   411  	}
   412  	defer os.Remove(fh.Name())
   413  
   414  	if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
   415  		t.Fatalf("err: %s", err)
   416  	}
   417  
   418  	// Works on a config file
   419  	config, err := LoadConfig(fh.Name())
   420  	if err != nil {
   421  		t.Fatalf("err: %s", err)
   422  	}
   423  	if config.Region != "west" {
   424  		t.Fatalf("bad: %#v", config)
   425  	}
   426  
   427  	expectedConfigFiles := []string{fh.Name()}
   428  	if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
   429  		t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
   430  			expectedConfigFiles, config.Files)
   431  	}
   432  
   433  	dir, err := ioutil.TempDir("", "nomad")
   434  	if err != nil {
   435  		t.Fatalf("err: %s", err)
   436  	}
   437  	defer os.RemoveAll(dir)
   438  
   439  	file1 := filepath.Join(dir, "config1.hcl")
   440  	err = ioutil.WriteFile(file1, []byte(`{"datacenter":"sfo"}`), 0600)
   441  	if err != nil {
   442  		t.Fatalf("err: %s", err)
   443  	}
   444  
   445  	// Works on config dir
   446  	config, err = LoadConfig(dir)
   447  	if err != nil {
   448  		t.Fatalf("err: %s", err)
   449  	}
   450  	if config.Datacenter != "sfo" {
   451  		t.Fatalf("bad: %#v", config)
   452  	}
   453  
   454  	expectedConfigFiles = []string{file1}
   455  	if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
   456  		t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
   457  			expectedConfigFiles, config.Files)
   458  	}
   459  }
   460  
   461  func TestConfig_LoadConfigsFileOrder(t *testing.T) {
   462  	config1, err := LoadConfigDir("test-resources/etcnomad")
   463  	if err != nil {
   464  		t.Fatalf("Failed to load config: %s", err)
   465  	}
   466  
   467  	config2, err := LoadConfig("test-resources/myconf")
   468  	if err != nil {
   469  		t.Fatalf("Failed to load config: %s", err)
   470  	}
   471  
   472  	expected := []string{
   473  		// filepath.FromSlash changes these to backslash \ on Windows
   474  		filepath.FromSlash("test-resources/etcnomad/common.hcl"),
   475  		filepath.FromSlash("test-resources/etcnomad/server.json"),
   476  		filepath.FromSlash("test-resources/myconf"),
   477  	}
   478  
   479  	config := config1.Merge(config2)
   480  
   481  	if !reflect.DeepEqual(config.Files, expected) {
   482  		t.Errorf("Loaded configs don't match\nwant: %+v\n got: %+v\n",
   483  			expected, config.Files)
   484  	}
   485  }
   486  
   487  func TestConfig_Listener(t *testing.T) {
   488  	config := DefaultConfig()
   489  
   490  	// Fails on invalid input
   491  	if ln, err := config.Listener("tcp", "nope", 8080); err == nil {
   492  		ln.Close()
   493  		t.Fatalf("expected addr error")
   494  	}
   495  	if ln, err := config.Listener("nope", "127.0.0.1", 8080); err == nil {
   496  		ln.Close()
   497  		t.Fatalf("expected protocol err")
   498  	}
   499  	if ln, err := config.Listener("tcp", "127.0.0.1", -1); err == nil {
   500  		ln.Close()
   501  		t.Fatalf("expected port error")
   502  	}
   503  
   504  	// Works with valid inputs
   505  	ln, err := config.Listener("tcp", "127.0.0.1", 24000)
   506  	if err != nil {
   507  		t.Fatalf("err: %s", err)
   508  	}
   509  	ln.Close()
   510  
   511  	if net := ln.Addr().Network(); net != "tcp" {
   512  		t.Fatalf("expected tcp, got: %q", net)
   513  	}
   514  	if addr := ln.Addr().String(); addr != "127.0.0.1:24000" {
   515  		t.Fatalf("expected 127.0.0.1:4646, got: %q", addr)
   516  	}
   517  
   518  	// Falls back to default bind address if non provided
   519  	config.BindAddr = "0.0.0.0"
   520  	ln, err = config.Listener("tcp4", "", 24000)
   521  	if err != nil {
   522  		t.Fatalf("err: %s", err)
   523  	}
   524  	ln.Close()
   525  
   526  	if addr := ln.Addr().String(); addr != "0.0.0.0:24000" {
   527  		t.Fatalf("expected 0.0.0.0:24000, got: %q", addr)
   528  	}
   529  }
   530  
   531  // TestConfig_normalizeAddrs_DevMode asserts that normalizeAddrs allows
   532  // advertising localhost in dev mode.
   533  func TestConfig_normalizeAddrs_DevMode(t *testing.T) {
   534  	// allow to advertise 127.0.0.1 if dev-mode is enabled
   535  	c := &Config{
   536  		BindAddr: "127.0.0.1",
   537  		Ports: &Ports{
   538  			HTTP: 4646,
   539  			RPC:  4647,
   540  			Serf: 4648,
   541  		},
   542  		Addresses:      &Addresses{},
   543  		AdvertiseAddrs: &AdvertiseAddrs{},
   544  		DevMode:        true,
   545  	}
   546  
   547  	if err := c.normalizeAddrs(); err != nil {
   548  		t.Fatalf("unable to normalize addresses: %s", err)
   549  	}
   550  
   551  	if c.BindAddr != "127.0.0.1" {
   552  		t.Fatalf("expected BindAddr 127.0.0.1, got %s", c.BindAddr)
   553  	}
   554  
   555  	if c.normalizedAddrs.HTTP != "127.0.0.1:4646" {
   556  		t.Fatalf("expected HTTP address 127.0.0.1:4646, got %s", c.normalizedAddrs.HTTP)
   557  	}
   558  
   559  	if c.normalizedAddrs.RPC != "127.0.0.1:4647" {
   560  		t.Fatalf("expected RPC address 127.0.0.1:4647, got %s", c.normalizedAddrs.RPC)
   561  	}
   562  
   563  	if c.normalizedAddrs.Serf != "127.0.0.1:4648" {
   564  		t.Fatalf("expected Serf address 127.0.0.1:4648, got %s", c.normalizedAddrs.Serf)
   565  	}
   566  
   567  	if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
   568  		t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
   569  	}
   570  
   571  	if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
   572  		t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
   573  	}
   574  
   575  	// Client mode, no Serf address defined
   576  	if c.AdvertiseAddrs.Serf != "" {
   577  		t.Fatalf("expected unset Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
   578  	}
   579  }
   580  
   581  // TestConfig_normalizeAddrs_NoAdvertise asserts that normalizeAddrs will
   582  // fail if no valid advertise address available in non-dev mode.
   583  func TestConfig_normalizeAddrs_NoAdvertise(t *testing.T) {
   584  	c := &Config{
   585  		BindAddr: "127.0.0.1",
   586  		Ports: &Ports{
   587  			HTTP: 4646,
   588  			RPC:  4647,
   589  			Serf: 4648,
   590  		},
   591  		Addresses:      &Addresses{},
   592  		AdvertiseAddrs: &AdvertiseAddrs{},
   593  		DevMode:        false,
   594  	}
   595  
   596  	if err := c.normalizeAddrs(); err == nil {
   597  		t.Fatalf("expected an error when no valid advertise address is available")
   598  	}
   599  
   600  	if c.AdvertiseAddrs.HTTP == "127.0.0.1:4646" {
   601  		t.Fatalf("expected non-localhost HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
   602  	}
   603  
   604  	if c.AdvertiseAddrs.RPC == "127.0.0.1:4647" {
   605  		t.Fatalf("expected non-localhost RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
   606  	}
   607  
   608  	if c.AdvertiseAddrs.Serf == "127.0.0.1:4648" {
   609  		t.Fatalf("expected non-localhost Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
   610  	}
   611  }
   612  
   613  // TestConfig_normalizeAddrs_AdvertiseLocalhost asserts localhost can be
   614  // advertised if it's explicitly set in the config.
   615  func TestConfig_normalizeAddrs_AdvertiseLocalhost(t *testing.T) {
   616  	c := &Config{
   617  		BindAddr: "127.0.0.1",
   618  		Ports: &Ports{
   619  			HTTP: 4646,
   620  			RPC:  4647,
   621  			Serf: 4648,
   622  		},
   623  		Addresses: &Addresses{},
   624  		AdvertiseAddrs: &AdvertiseAddrs{
   625  			HTTP: "127.0.0.1",
   626  			RPC:  "127.0.0.1",
   627  			Serf: "127.0.0.1",
   628  		},
   629  		DevMode: false,
   630  		Server:  &ServerConfig{Enabled: true},
   631  	}
   632  
   633  	if err := c.normalizeAddrs(); err != nil {
   634  		t.Fatalf("unexpected error when manually setting bind mode: %v", err)
   635  	}
   636  
   637  	if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
   638  		t.Errorf("expected localhost HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
   639  	}
   640  
   641  	if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
   642  		t.Errorf("expected localhost RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
   643  	}
   644  
   645  	if c.AdvertiseAddrs.Serf != "127.0.0.1:4648" {
   646  		t.Errorf("expected localhost Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
   647  	}
   648  }
   649  
   650  // TestConfig_normalizeAddrs_IPv6Loopback asserts that an IPv6 loopback address
   651  // is normalized properly. See #2739
   652  func TestConfig_normalizeAddrs_IPv6Loopback(t *testing.T) {
   653  	c := &Config{
   654  		BindAddr: "::1",
   655  		Ports: &Ports{
   656  			HTTP: 4646,
   657  			RPC:  4647,
   658  		},
   659  		Addresses: &Addresses{},
   660  		AdvertiseAddrs: &AdvertiseAddrs{
   661  			HTTP: "::1",
   662  			RPC:  "::1",
   663  		},
   664  		DevMode: false,
   665  	}
   666  
   667  	if err := c.normalizeAddrs(); err != nil {
   668  		t.Fatalf("unexpected error when manually setting bind mode: %v", err)
   669  	}
   670  
   671  	if c.Addresses.HTTP != "::1" {
   672  		t.Errorf("expected ::1 HTTP address, got %s", c.Addresses.HTTP)
   673  	}
   674  
   675  	if c.Addresses.RPC != "::1" {
   676  		t.Errorf("expected ::1 RPC address, got %s", c.Addresses.RPC)
   677  	}
   678  
   679  	if c.AdvertiseAddrs.HTTP != "[::1]:4646" {
   680  		t.Errorf("expected [::1] HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
   681  	}
   682  
   683  	if c.AdvertiseAddrs.RPC != "[::1]:4647" {
   684  		t.Errorf("expected [::1] RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
   685  	}
   686  }
   687  
   688  func TestConfig_normalizeAddrs(t *testing.T) {
   689  	c := &Config{
   690  		BindAddr: "169.254.1.5",
   691  		Ports: &Ports{
   692  			HTTP: 4646,
   693  			RPC:  4647,
   694  			Serf: 4648,
   695  		},
   696  		Addresses: &Addresses{
   697  			HTTP: "169.254.1.10",
   698  		},
   699  		AdvertiseAddrs: &AdvertiseAddrs{
   700  			RPC: "169.254.1.40",
   701  		},
   702  		Server: &ServerConfig{
   703  			Enabled: true,
   704  		},
   705  	}
   706  
   707  	if err := c.normalizeAddrs(); err != nil {
   708  		t.Fatalf("unable to normalize addresses: %s", err)
   709  	}
   710  
   711  	if c.BindAddr != "169.254.1.5" {
   712  		t.Fatalf("expected BindAddr 169.254.1.5, got %s", c.BindAddr)
   713  	}
   714  
   715  	if c.AdvertiseAddrs.HTTP != "169.254.1.10:4646" {
   716  		t.Fatalf("expected HTTP advertise address 169.254.1.10:4646, got %s", c.AdvertiseAddrs.HTTP)
   717  	}
   718  
   719  	if c.AdvertiseAddrs.RPC != "169.254.1.40:4647" {
   720  		t.Fatalf("expected RPC advertise address 169.254.1.40:4647, got %s", c.AdvertiseAddrs.RPC)
   721  	}
   722  
   723  	if c.AdvertiseAddrs.Serf != "169.254.1.5:4648" {
   724  		t.Fatalf("expected Serf advertise address 169.254.1.5:4648, got %s", c.AdvertiseAddrs.Serf)
   725  	}
   726  
   727  	c = &Config{
   728  		BindAddr: "{{ GetPrivateIP }}",
   729  		Ports: &Ports{
   730  			HTTP: 4646,
   731  			RPC:  4647,
   732  			Serf: 4648,
   733  		},
   734  		Addresses: &Addresses{},
   735  		AdvertiseAddrs: &AdvertiseAddrs{
   736  			RPC: "{{ GetPrivateIP }}:8888",
   737  		},
   738  		Server: &ServerConfig{
   739  			Enabled: true,
   740  		},
   741  	}
   742  
   743  	if err := c.normalizeAddrs(); err != nil {
   744  		t.Fatalf("unable to normalize addresses: %s", err)
   745  	}
   746  
   747  	if c.AdvertiseAddrs.HTTP != fmt.Sprintf("%s:4646", c.BindAddr) {
   748  		t.Fatalf("expected HTTP advertise address %s:4646, got %s", c.BindAddr, c.AdvertiseAddrs.HTTP)
   749  	}
   750  
   751  	if c.AdvertiseAddrs.RPC != fmt.Sprintf("%s:8888", c.BindAddr) {
   752  		t.Fatalf("expected RPC advertise address %s:8888, got %s", c.BindAddr, c.AdvertiseAddrs.RPC)
   753  	}
   754  
   755  	if c.AdvertiseAddrs.Serf != fmt.Sprintf("%s:4648", c.BindAddr) {
   756  		t.Fatalf("expected Serf advertise address %s:4648, got %s", c.BindAddr, c.AdvertiseAddrs.Serf)
   757  	}
   758  
   759  	// allow to advertise 127.0.0.1 in non-dev mode, if explicitly configured to do so
   760  	c = &Config{
   761  		BindAddr: "127.0.0.1",
   762  		Ports: &Ports{
   763  			HTTP: 4646,
   764  			RPC:  4647,
   765  			Serf: 4648,
   766  		},
   767  		Addresses: &Addresses{},
   768  		AdvertiseAddrs: &AdvertiseAddrs{
   769  			HTTP: "127.0.0.1:4646",
   770  			RPC:  "127.0.0.1:4647",
   771  			Serf: "127.0.0.1:4648",
   772  		},
   773  		DevMode: false,
   774  		Server: &ServerConfig{
   775  			Enabled: true,
   776  		},
   777  	}
   778  
   779  	if err := c.normalizeAddrs(); err != nil {
   780  		t.Fatalf("unable to normalize addresses: %s", err)
   781  	}
   782  
   783  	if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
   784  		t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
   785  	}
   786  
   787  	if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
   788  		t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
   789  	}
   790  
   791  	if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
   792  		t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
   793  	}
   794  }
   795  
   796  func TestResources_ParseReserved(t *testing.T) {
   797  	cases := []struct {
   798  		Input  string
   799  		Parsed []int
   800  		Err    bool
   801  	}{
   802  		{
   803  			"1,2,3",
   804  			[]int{1, 2, 3},
   805  			false,
   806  		},
   807  		{
   808  			"3,1,2,1,2,3,1-3",
   809  			[]int{1, 2, 3},
   810  			false,
   811  		},
   812  		{
   813  			"3-1",
   814  			nil,
   815  			true,
   816  		},
   817  		{
   818  			"1-3,2-4",
   819  			[]int{1, 2, 3, 4},
   820  			false,
   821  		},
   822  		{
   823  			"1-3,4,5-5,6,7,8-10",
   824  			[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   825  			false,
   826  		},
   827  	}
   828  
   829  	for i, tc := range cases {
   830  		r := &Resources{ReservedPorts: tc.Input}
   831  		err := r.ParseReserved()
   832  		if (err != nil) != tc.Err {
   833  			t.Fatalf("test case %d: %v", i, err)
   834  			continue
   835  		}
   836  
   837  		if !reflect.DeepEqual(r.ParsedReservedPorts, tc.Parsed) {
   838  			t.Fatalf("test case %d: \n\n%#v\n\n%#v", i, r.ParsedReservedPorts, tc.Parsed)
   839  		}
   840  
   841  	}
   842  }
   843  
   844  func TestIsMissingPort(t *testing.T) {
   845  	_, _, err := net.SplitHostPort("localhost")
   846  	if missing := isMissingPort(err); !missing {
   847  		t.Errorf("expected missing port error, but got %v", err)
   848  	}
   849  	_, _, err = net.SplitHostPort("localhost:9000")
   850  	if missing := isMissingPort(err); missing {
   851  		t.Errorf("expected no error, but got %v", err)
   852  	}
   853  }