github.com/mongey/nomad@v0.5.2/command/agent/config_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"io/ioutil"
     5  	"net"
     6  	"os"
     7  	"path/filepath"
     8  	"reflect"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/hashicorp/nomad/nomad/structs/config"
    14  )
    15  
    16  var (
    17  	// trueValue/falseValue are used to get a pointer to a boolean
    18  	trueValue  = true
    19  	falseValue = false
    20  )
    21  
    22  func TestConfig_Merge(t *testing.T) {
    23  	c1 := &Config{
    24  		Region:                    "global",
    25  		Datacenter:                "dc1",
    26  		NodeName:                  "node1",
    27  		DataDir:                   "/tmp/dir1",
    28  		LogLevel:                  "INFO",
    29  		EnableDebug:               false,
    30  		LeaveOnInt:                false,
    31  		LeaveOnTerm:               false,
    32  		EnableSyslog:              false,
    33  		SyslogFacility:            "local0.info",
    34  		DisableUpdateCheck:        false,
    35  		DisableAnonymousSignature: false,
    36  		BindAddr:                  "127.0.0.1",
    37  		Telemetry: &Telemetry{
    38  			StatsiteAddr:                       "127.0.0.1:8125",
    39  			StatsdAddr:                         "127.0.0.1:8125",
    40  			DataDogAddr:                        "127.0.0.1:8125",
    41  			DisableHostname:                    false,
    42  			CirconusAPIToken:                   "0",
    43  			CirconusAPIApp:                     "nomadic",
    44  			CirconusAPIURL:                     "http://api.circonus.com/v2",
    45  			CirconusSubmissionInterval:         "60s",
    46  			CirconusCheckSubmissionURL:         "https://someplace.com/metrics",
    47  			CirconusCheckID:                    "0",
    48  			CirconusCheckForceMetricActivation: "true",
    49  			CirconusCheckInstanceID:            "node1:nomadic",
    50  			CirconusCheckSearchTag:             "service:nomadic",
    51  			CirconusCheckDisplayName:           "node1:nomadic",
    52  			CirconusCheckTags:                  "cat1:tag1,cat2:tag2",
    53  			CirconusBrokerID:                   "0",
    54  			CirconusBrokerSelectTag:            "dc:dc1",
    55  		},
    56  		Client: &ClientConfig{
    57  			Enabled:   false,
    58  			StateDir:  "/tmp/state1",
    59  			AllocDir:  "/tmp/alloc1",
    60  			NodeClass: "class1",
    61  			Options: map[string]string{
    62  				"foo": "bar",
    63  			},
    64  			NetworkSpeed:   100,
    65  			MaxKillTimeout: "20s",
    66  			ClientMaxPort:  19996,
    67  			Reserved: &Resources{
    68  				CPU:                 10,
    69  				MemoryMB:            10,
    70  				DiskMB:              10,
    71  				IOPS:                10,
    72  				ReservedPorts:       "1,10-30,55",
    73  				ParsedReservedPorts: []int{1, 2, 4},
    74  			},
    75  		},
    76  		Server: &ServerConfig{
    77  			Enabled:         false,
    78  			BootstrapExpect: 1,
    79  			DataDir:         "/tmp/data1",
    80  			ProtocolVersion: 1,
    81  			NumSchedulers:   1,
    82  			NodeGCThreshold: "1h",
    83  			HeartbeatGrace:  "30s",
    84  		},
    85  		Ports: &Ports{
    86  			HTTP: 4646,
    87  			RPC:  4647,
    88  			Serf: 4648,
    89  		},
    90  		Addresses: &Addresses{
    91  			HTTP: "127.0.0.1",
    92  			RPC:  "127.0.0.1",
    93  			Serf: "127.0.0.1",
    94  		},
    95  		AdvertiseAddrs: &AdvertiseAddrs{
    96  			RPC:  "127.0.0.1",
    97  			Serf: "127.0.0.1",
    98  		},
    99  		Atlas: &AtlasConfig{
   100  			Infrastructure: "hashicorp/test1",
   101  			Token:          "abc",
   102  			Join:           false,
   103  			Endpoint:       "foo",
   104  		},
   105  		HTTPAPIResponseHeaders: map[string]string{
   106  			"Access-Control-Allow-Origin": "*",
   107  		},
   108  		Vault: &config.VaultConfig{
   109  			Token:                "1",
   110  			AllowUnauthenticated: &falseValue,
   111  			TaskTokenTTL:         "1",
   112  			Addr:                 "1",
   113  			TLSCaFile:            "1",
   114  			TLSCaPath:            "1",
   115  			TLSCertFile:          "1",
   116  			TLSKeyFile:           "1",
   117  			TLSSkipVerify:        &falseValue,
   118  			TLSServerName:        "1",
   119  		},
   120  		Consul: &config.ConsulConfig{
   121  			ServerServiceName: "1",
   122  			ClientServiceName: "1",
   123  			AutoAdvertise:     false,
   124  			Addr:              "1",
   125  			Timeout:           1 * time.Second,
   126  			Token:             "1",
   127  			Auth:              "1",
   128  			EnableSSL:         false,
   129  			VerifySSL:         false,
   130  			CAFile:            "1",
   131  			CertFile:          "1",
   132  			KeyFile:           "1",
   133  			ServerAutoJoin:    false,
   134  			ClientAutoJoin:    false,
   135  		},
   136  	}
   137  
   138  	c2 := &Config{
   139  		Region:                    "region2",
   140  		Datacenter:                "dc2",
   141  		NodeName:                  "node2",
   142  		DataDir:                   "/tmp/dir2",
   143  		LogLevel:                  "DEBUG",
   144  		EnableDebug:               true,
   145  		LeaveOnInt:                true,
   146  		LeaveOnTerm:               true,
   147  		EnableSyslog:              true,
   148  		SyslogFacility:            "local0.debug",
   149  		DisableUpdateCheck:        true,
   150  		DisableAnonymousSignature: true,
   151  		BindAddr:                  "127.0.0.2",
   152  		Telemetry: &Telemetry{
   153  			StatsiteAddr:                       "127.0.0.2:8125",
   154  			StatsdAddr:                         "127.0.0.2:8125",
   155  			DataDogAddr:                        "127.0.0.1:8125",
   156  			DisableHostname:                    true,
   157  			PublishNodeMetrics:                 true,
   158  			PublishAllocationMetrics:           true,
   159  			CirconusAPIToken:                   "1",
   160  			CirconusAPIApp:                     "nomad",
   161  			CirconusAPIURL:                     "https://api.circonus.com/v2",
   162  			CirconusSubmissionInterval:         "10s",
   163  			CirconusCheckSubmissionURL:         "https://example.com/metrics",
   164  			CirconusCheckID:                    "1",
   165  			CirconusCheckForceMetricActivation: "false",
   166  			CirconusCheckInstanceID:            "node2:nomad",
   167  			CirconusCheckSearchTag:             "service:nomad",
   168  			CirconusCheckDisplayName:           "node2:nomad",
   169  			CirconusCheckTags:                  "cat1:tag1,cat2:tag2",
   170  			CirconusBrokerID:                   "1",
   171  			CirconusBrokerSelectTag:            "dc:dc2",
   172  		},
   173  		Client: &ClientConfig{
   174  			Enabled:   true,
   175  			StateDir:  "/tmp/state2",
   176  			AllocDir:  "/tmp/alloc2",
   177  			NodeClass: "class2",
   178  			Servers:   []string{"server2"},
   179  			Meta: map[string]string{
   180  				"baz": "zip",
   181  			},
   182  			Options: map[string]string{
   183  				"foo": "bar",
   184  				"baz": "zip",
   185  			},
   186  			ChrootEnv:      map[string]string{},
   187  			ClientMaxPort:  20000,
   188  			ClientMinPort:  22000,
   189  			NetworkSpeed:   105,
   190  			MaxKillTimeout: "50s",
   191  			Reserved: &Resources{
   192  				CPU:                 15,
   193  				MemoryMB:            15,
   194  				DiskMB:              15,
   195  				IOPS:                15,
   196  				ReservedPorts:       "2,10-30,55",
   197  				ParsedReservedPorts: []int{1, 2, 3},
   198  			},
   199  		},
   200  		Server: &ServerConfig{
   201  			Enabled:           true,
   202  			BootstrapExpect:   2,
   203  			DataDir:           "/tmp/data2",
   204  			ProtocolVersion:   2,
   205  			NumSchedulers:     2,
   206  			EnabledSchedulers: []string{structs.JobTypeBatch},
   207  			NodeGCThreshold:   "12h",
   208  			HeartbeatGrace:    "2m",
   209  			RejoinAfterLeave:  true,
   210  			StartJoin:         []string{"1.1.1.1"},
   211  			RetryJoin:         []string{"1.1.1.1"},
   212  			RetryInterval:     "10s",
   213  			retryInterval:     time.Second * 10,
   214  		},
   215  		Ports: &Ports{
   216  			HTTP: 20000,
   217  			RPC:  21000,
   218  			Serf: 22000,
   219  		},
   220  		Addresses: &Addresses{
   221  			HTTP: "127.0.0.2",
   222  			RPC:  "127.0.0.2",
   223  			Serf: "127.0.0.2",
   224  		},
   225  		AdvertiseAddrs: &AdvertiseAddrs{
   226  			RPC:  "127.0.0.2",
   227  			Serf: "127.0.0.2",
   228  		},
   229  		Atlas: &AtlasConfig{
   230  			Infrastructure: "hashicorp/test2",
   231  			Token:          "xyz",
   232  			Join:           true,
   233  			Endpoint:       "bar",
   234  		},
   235  		HTTPAPIResponseHeaders: map[string]string{
   236  			"Access-Control-Allow-Origin":  "*",
   237  			"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
   238  		},
   239  		Vault: &config.VaultConfig{
   240  			Token:                "2",
   241  			AllowUnauthenticated: &trueValue,
   242  			TaskTokenTTL:         "2",
   243  			Addr:                 "2",
   244  			TLSCaFile:            "2",
   245  			TLSCaPath:            "2",
   246  			TLSCertFile:          "2",
   247  			TLSKeyFile:           "2",
   248  			TLSSkipVerify:        &trueValue,
   249  			TLSServerName:        "2",
   250  		},
   251  		Consul: &config.ConsulConfig{
   252  			ServerServiceName: "2",
   253  			ClientServiceName: "2",
   254  			AutoAdvertise:     true,
   255  			Addr:              "2",
   256  			Timeout:           2 * time.Second,
   257  			Token:             "2",
   258  			Auth:              "2",
   259  			EnableSSL:         true,
   260  			VerifySSL:         true,
   261  			CAFile:            "2",
   262  			CertFile:          "2",
   263  			KeyFile:           "2",
   264  			ServerAutoJoin:    true,
   265  			ClientAutoJoin:    true,
   266  		},
   267  	}
   268  
   269  	result := c1.Merge(c2)
   270  	if !reflect.DeepEqual(result, c2) {
   271  		t.Fatalf("bad:\n%#v\n%#v", result, c2)
   272  	}
   273  }
   274  
   275  func TestConfig_ParseConfigFile(t *testing.T) {
   276  	// Fails if the file doesn't exist
   277  	if _, err := ParseConfigFile("/unicorns/leprechauns"); err == nil {
   278  		t.Fatalf("expected error, got nothing")
   279  	}
   280  
   281  	fh, err := ioutil.TempFile("", "nomad")
   282  	if err != nil {
   283  		t.Fatalf("err: %s", err)
   284  	}
   285  	defer os.RemoveAll(fh.Name())
   286  
   287  	// Invalid content returns error
   288  	if _, err := fh.WriteString("nope;!!!"); err != nil {
   289  		t.Fatalf("err: %s", err)
   290  	}
   291  	if _, err := ParseConfigFile(fh.Name()); err == nil {
   292  		t.Fatalf("expected load error, got nothing")
   293  	}
   294  
   295  	// Valid content parses successfully
   296  	if err := fh.Truncate(0); err != nil {
   297  		t.Fatalf("err: %s", err)
   298  	}
   299  	if _, err := fh.Seek(0, 0); err != nil {
   300  		t.Fatalf("err: %s", err)
   301  	}
   302  	if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
   303  		t.Fatalf("err: %s", err)
   304  	}
   305  
   306  	config, err := ParseConfigFile(fh.Name())
   307  	if err != nil {
   308  		t.Fatalf("err: %s", err)
   309  	}
   310  	if config.Region != "west" {
   311  		t.Fatalf("bad region: %q", config.Region)
   312  	}
   313  }
   314  
   315  func TestConfig_LoadConfigDir(t *testing.T) {
   316  	// Fails if the dir doesn't exist.
   317  	if _, err := LoadConfigDir("/unicorns/leprechauns"); err == nil {
   318  		t.Fatalf("expected error, got nothing")
   319  	}
   320  
   321  	dir, err := ioutil.TempDir("", "nomad")
   322  	if err != nil {
   323  		t.Fatalf("err: %s", err)
   324  	}
   325  	defer os.RemoveAll(dir)
   326  
   327  	// Returns empty config on empty dir
   328  	config, err := LoadConfig(dir)
   329  	if err != nil {
   330  		t.Fatalf("err: %s", err)
   331  	}
   332  	if config == nil {
   333  		t.Fatalf("should not be nil")
   334  	}
   335  
   336  	file1 := filepath.Join(dir, "conf1.hcl")
   337  	err = ioutil.WriteFile(file1, []byte(`{"region":"west"}`), 0600)
   338  	if err != nil {
   339  		t.Fatalf("err: %s", err)
   340  	}
   341  
   342  	file2 := filepath.Join(dir, "conf2.hcl")
   343  	err = ioutil.WriteFile(file2, []byte(`{"datacenter":"sfo"}`), 0600)
   344  	if err != nil {
   345  		t.Fatalf("err: %s", err)
   346  	}
   347  
   348  	file3 := filepath.Join(dir, "conf3.hcl")
   349  	err = ioutil.WriteFile(file3, []byte(`nope;!!!`), 0600)
   350  	if err != nil {
   351  		t.Fatalf("err: %s", err)
   352  	}
   353  
   354  	// Fails if we have a bad config file
   355  	if _, err := LoadConfigDir(dir); err == nil {
   356  		t.Fatalf("expected load error, got nothing")
   357  	}
   358  
   359  	if err := os.Remove(file3); err != nil {
   360  		t.Fatalf("err: %s", err)
   361  	}
   362  
   363  	// Works if configs are valid
   364  	config, err = LoadConfigDir(dir)
   365  	if err != nil {
   366  		t.Fatalf("err: %s", err)
   367  	}
   368  	if config.Region != "west" || config.Datacenter != "sfo" {
   369  		t.Fatalf("bad: %#v", config)
   370  	}
   371  }
   372  
   373  func TestConfig_LoadConfig(t *testing.T) {
   374  	// Fails if the target doesn't exist
   375  	if _, err := LoadConfig("/unicorns/leprechauns"); err == nil {
   376  		t.Fatalf("expected error, got nothing")
   377  	}
   378  
   379  	fh, err := ioutil.TempFile("", "nomad")
   380  	if err != nil {
   381  		t.Fatalf("err: %s", err)
   382  	}
   383  	defer os.Remove(fh.Name())
   384  
   385  	if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
   386  		t.Fatalf("err: %s", err)
   387  	}
   388  
   389  	// Works on a config file
   390  	config, err := LoadConfig(fh.Name())
   391  	if err != nil {
   392  		t.Fatalf("err: %s", err)
   393  	}
   394  	if config.Region != "west" {
   395  		t.Fatalf("bad: %#v", config)
   396  	}
   397  
   398  	expectedConfigFiles := []string{fh.Name()}
   399  	if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
   400  		t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
   401  			expectedConfigFiles, config.Files)
   402  	}
   403  
   404  	dir, err := ioutil.TempDir("", "nomad")
   405  	if err != nil {
   406  		t.Fatalf("err: %s", err)
   407  	}
   408  	defer os.RemoveAll(dir)
   409  
   410  	file1 := filepath.Join(dir, "config1.hcl")
   411  	err = ioutil.WriteFile(file1, []byte(`{"datacenter":"sfo"}`), 0600)
   412  	if err != nil {
   413  		t.Fatalf("err: %s", err)
   414  	}
   415  
   416  	// Works on config dir
   417  	config, err = LoadConfig(dir)
   418  	if err != nil {
   419  		t.Fatalf("err: %s", err)
   420  	}
   421  	if config.Datacenter != "sfo" {
   422  		t.Fatalf("bad: %#v", config)
   423  	}
   424  
   425  	expectedConfigFiles = []string{file1}
   426  	if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
   427  		t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
   428  			expectedConfigFiles, config.Files)
   429  	}
   430  }
   431  
   432  func TestConfig_LoadConfigsFileOrder(t *testing.T) {
   433  	config1, err := LoadConfigDir("test-resources/etcnomad")
   434  	if err != nil {
   435  		t.Fatalf("Failed to load config: %s", err)
   436  	}
   437  
   438  	config2, err := LoadConfig("test-resources/myconf")
   439  	if err != nil {
   440  		t.Fatalf("Failed to load config: %s", err)
   441  	}
   442  
   443  	expected := []string{
   444  		// filepath.FromSlash changes these to backslash \ on Windows
   445  		filepath.FromSlash("test-resources/etcnomad/common.hcl"),
   446  		filepath.FromSlash("test-resources/etcnomad/server.json"),
   447  		filepath.FromSlash("test-resources/myconf"),
   448  	}
   449  
   450  	config := config1.Merge(config2)
   451  
   452  	if !reflect.DeepEqual(config.Files, expected) {
   453  		t.Errorf("Loaded configs don't match\nwant: %+v\n got: %+v\n",
   454  			expected, config.Files)
   455  	}
   456  }
   457  
   458  func TestConfig_Listener(t *testing.T) {
   459  	config := DefaultConfig()
   460  
   461  	// Fails on invalid input
   462  	if ln, err := config.Listener("tcp", "nope", 8080); err == nil {
   463  		ln.Close()
   464  		t.Fatalf("expected addr error")
   465  	}
   466  	if ln, err := config.Listener("nope", "127.0.0.1", 8080); err == nil {
   467  		ln.Close()
   468  		t.Fatalf("expected protocol err")
   469  	}
   470  	if ln, err := config.Listener("tcp", "127.0.0.1", -1); err == nil {
   471  		ln.Close()
   472  		t.Fatalf("expected port error")
   473  	}
   474  
   475  	// Works with valid inputs
   476  	ln, err := config.Listener("tcp", "127.0.0.1", 24000)
   477  	if err != nil {
   478  		t.Fatalf("err: %s", err)
   479  	}
   480  	ln.Close()
   481  
   482  	if net := ln.Addr().Network(); net != "tcp" {
   483  		t.Fatalf("expected tcp, got: %q", net)
   484  	}
   485  	if addr := ln.Addr().String(); addr != "127.0.0.1:24000" {
   486  		t.Fatalf("expected 127.0.0.1:4646, got: %q", addr)
   487  	}
   488  
   489  	// Falls back to default bind address if non provided
   490  	config.BindAddr = "0.0.0.0"
   491  	ln, err = config.Listener("tcp4", "", 24000)
   492  	if err != nil {
   493  		t.Fatalf("err: %s", err)
   494  	}
   495  	ln.Close()
   496  
   497  	if addr := ln.Addr().String(); addr != "0.0.0.0:24000" {
   498  		t.Fatalf("expected 0.0.0.0:24000, got: %q", addr)
   499  	}
   500  }
   501  
   502  func TestResources_ParseReserved(t *testing.T) {
   503  	cases := []struct {
   504  		Input  string
   505  		Parsed []int
   506  		Err    bool
   507  	}{
   508  		{
   509  			"1,2,3",
   510  			[]int{1, 2, 3},
   511  			false,
   512  		},
   513  		{
   514  			"3,1,2,1,2,3,1-3",
   515  			[]int{1, 2, 3},
   516  			false,
   517  		},
   518  		{
   519  			"3-1",
   520  			nil,
   521  			true,
   522  		},
   523  		{
   524  			"1-3,2-4",
   525  			[]int{1, 2, 3, 4},
   526  			false,
   527  		},
   528  		{
   529  			"1-3,4,5-5,6,7,8-10",
   530  			[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   531  			false,
   532  		},
   533  	}
   534  
   535  	for i, tc := range cases {
   536  		r := &Resources{ReservedPorts: tc.Input}
   537  		err := r.ParseReserved()
   538  		if (err != nil) != tc.Err {
   539  			t.Fatalf("test case %d: %v", i, err)
   540  			continue
   541  		}
   542  
   543  		if !reflect.DeepEqual(r.ParsedReservedPorts, tc.Parsed) {
   544  			t.Fatalf("test case %d: \n\n%#v\n\n%#v", i, r.ParsedReservedPorts, tc.Parsed)
   545  		}
   546  
   547  	}
   548  }
   549  
   550  func TestIsMissingPort(t *testing.T) {
   551  	_, _, err := net.SplitHostPort("localhost")
   552  	if missing := isMissingPort(err); !missing {
   553  		t.Errorf("expected missing port error, but got %v", err)
   554  	}
   555  	_, _, err = net.SplitHostPort("localhost:9000")
   556  	if missing := isMissingPort(err); missing {
   557  		t.Errorf("expected no error, but got %v", err)
   558  	}
   559  }