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