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