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