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