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