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