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