github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/config_test.go (about)

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