github.com/bigcommerce/nomad@v0.9.3-bc/nomad/server_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
    13  	"github.com/hashicorp/nomad/helper/testlog"
    14  	"github.com/hashicorp/nomad/helper/uuid"
    15  	"github.com/hashicorp/nomad/nomad/mock"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/nomad/structs/config"
    18  	"github.com/hashicorp/nomad/testutil"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func tmpDir(t *testing.T) string {
    24  	t.Helper()
    25  	dir, err := ioutil.TempDir("", "nomad")
    26  	if err != nil {
    27  		t.Fatalf("err: %v", err)
    28  	}
    29  	return dir
    30  }
    31  
    32  func TestServer_RPC(t *testing.T) {
    33  	t.Parallel()
    34  	s1 := TestServer(t, nil)
    35  	defer s1.Shutdown()
    36  
    37  	var out struct{}
    38  	if err := s1.RPC("Status.Ping", struct{}{}, &out); err != nil {
    39  		t.Fatalf("err: %v", err)
    40  	}
    41  }
    42  
    43  func TestServer_RPC_TLS(t *testing.T) {
    44  	t.Parallel()
    45  	const (
    46  		cafile  = "../helper/tlsutil/testdata/ca.pem"
    47  		foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
    48  		fookey  = "../helper/tlsutil/testdata/nomad-foo-key.pem"
    49  	)
    50  	dir := tmpDir(t)
    51  	defer os.RemoveAll(dir)
    52  	s1 := TestServer(t, func(c *Config) {
    53  		c.Region = "regionFoo"
    54  		c.BootstrapExpect = 3
    55  		c.DevMode = false
    56  		c.DevDisableBootstrap = true
    57  		c.DataDir = path.Join(dir, "node1")
    58  		c.TLSConfig = &config.TLSConfig{
    59  			EnableHTTP:           true,
    60  			EnableRPC:            true,
    61  			VerifyServerHostname: true,
    62  			CAFile:               cafile,
    63  			CertFile:             foocert,
    64  			KeyFile:              fookey,
    65  		}
    66  	})
    67  	defer s1.Shutdown()
    68  
    69  	s2 := TestServer(t, func(c *Config) {
    70  		c.Region = "regionFoo"
    71  		c.BootstrapExpect = 3
    72  		c.DevMode = false
    73  		c.DevDisableBootstrap = true
    74  		c.DataDir = path.Join(dir, "node2")
    75  		c.TLSConfig = &config.TLSConfig{
    76  			EnableHTTP:           true,
    77  			EnableRPC:            true,
    78  			VerifyServerHostname: true,
    79  			CAFile:               cafile,
    80  			CertFile:             foocert,
    81  			KeyFile:              fookey,
    82  		}
    83  	})
    84  	defer s2.Shutdown()
    85  	s3 := TestServer(t, func(c *Config) {
    86  		c.Region = "regionFoo"
    87  		c.BootstrapExpect = 3
    88  		c.DevMode = false
    89  		c.DevDisableBootstrap = true
    90  		c.DataDir = path.Join(dir, "node3")
    91  		c.TLSConfig = &config.TLSConfig{
    92  			EnableHTTP:           true,
    93  			EnableRPC:            true,
    94  			VerifyServerHostname: true,
    95  			CAFile:               cafile,
    96  			CertFile:             foocert,
    97  			KeyFile:              fookey,
    98  		}
    99  	})
   100  	defer s3.Shutdown()
   101  
   102  	TestJoin(t, s1, s2, s3)
   103  	testutil.WaitForLeader(t, s1.RPC)
   104  
   105  	// Part of a server joining is making an RPC request, so just by testing
   106  	// that there is a leader we verify that the RPCs are working over TLS.
   107  }
   108  
   109  func TestServer_RPC_MixedTLS(t *testing.T) {
   110  	t.Parallel()
   111  	const (
   112  		cafile  = "../helper/tlsutil/testdata/ca.pem"
   113  		foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
   114  		fookey  = "../helper/tlsutil/testdata/nomad-foo-key.pem"
   115  	)
   116  	dir := tmpDir(t)
   117  	defer os.RemoveAll(dir)
   118  	s1 := TestServer(t, func(c *Config) {
   119  		c.Region = "regionFoo"
   120  		c.BootstrapExpect = 3
   121  		c.DevMode = false
   122  		c.DevDisableBootstrap = true
   123  		c.DataDir = path.Join(dir, "node1")
   124  		c.TLSConfig = &config.TLSConfig{
   125  			EnableHTTP:           true,
   126  			EnableRPC:            true,
   127  			VerifyServerHostname: true,
   128  			CAFile:               cafile,
   129  			CertFile:             foocert,
   130  			KeyFile:              fookey,
   131  		}
   132  	})
   133  	defer s1.Shutdown()
   134  
   135  	s2 := TestServer(t, func(c *Config) {
   136  		c.Region = "regionFoo"
   137  		c.BootstrapExpect = 3
   138  		c.DevMode = false
   139  		c.DevDisableBootstrap = true
   140  		c.DataDir = path.Join(dir, "node2")
   141  		c.TLSConfig = &config.TLSConfig{
   142  			EnableHTTP:           true,
   143  			EnableRPC:            true,
   144  			VerifyServerHostname: true,
   145  			CAFile:               cafile,
   146  			CertFile:             foocert,
   147  			KeyFile:              fookey,
   148  		}
   149  	})
   150  	defer s2.Shutdown()
   151  	s3 := TestServer(t, func(c *Config) {
   152  		c.Region = "regionFoo"
   153  		c.BootstrapExpect = 3
   154  		c.DevMode = false
   155  		c.DevDisableBootstrap = true
   156  		c.DataDir = path.Join(dir, "node3")
   157  	})
   158  	defer s3.Shutdown()
   159  
   160  	TestJoin(t, s1, s2, s3)
   161  
   162  	// Ensure that we do not form a quorum
   163  	start := time.Now()
   164  	for {
   165  		if time.Now().After(start.Add(2 * time.Second)) {
   166  			break
   167  		}
   168  
   169  		args := &structs.GenericRequest{}
   170  		var leader string
   171  		err := s1.RPC("Status.Leader", args, &leader)
   172  		if err == nil || leader != "" {
   173  			t.Fatalf("Got leader or no error: %q %v", leader, err)
   174  		}
   175  	}
   176  }
   177  
   178  func TestServer_Regions(t *testing.T) {
   179  	t.Parallel()
   180  	// Make the servers
   181  	s1 := TestServer(t, func(c *Config) {
   182  		c.Region = "region1"
   183  	})
   184  	defer s1.Shutdown()
   185  
   186  	s2 := TestServer(t, func(c *Config) {
   187  		c.Region = "region2"
   188  	})
   189  	defer s2.Shutdown()
   190  
   191  	// Join them together
   192  	s2Addr := fmt.Sprintf("127.0.0.1:%d",
   193  		s2.config.SerfConfig.MemberlistConfig.BindPort)
   194  	if n, err := s1.Join([]string{s2Addr}); err != nil || n != 1 {
   195  		t.Fatalf("Failed joining: %v (%d joined)", err, n)
   196  	}
   197  
   198  	// Try listing the regions
   199  	testutil.WaitForResult(func() (bool, error) {
   200  		out := s1.Regions()
   201  		if len(out) != 2 || out[0] != "region1" || out[1] != "region2" {
   202  			return false, fmt.Errorf("unexpected regions: %v", out)
   203  		}
   204  		return true, nil
   205  	}, func(err error) {
   206  		t.Fatalf("err: %v", err)
   207  	})
   208  }
   209  
   210  func TestServer_Reload_Vault(t *testing.T) {
   211  	t.Parallel()
   212  	s1 := TestServer(t, func(c *Config) {
   213  		c.Region = "region1"
   214  	})
   215  	defer s1.Shutdown()
   216  
   217  	if s1.vault.Running() {
   218  		t.Fatalf("Vault client should not be running")
   219  	}
   220  
   221  	tr := true
   222  	config := DefaultConfig()
   223  	config.VaultConfig.Enabled = &tr
   224  	config.VaultConfig.Token = uuid.Generate()
   225  
   226  	if err := s1.Reload(config); err != nil {
   227  		t.Fatalf("Reload failed: %v", err)
   228  	}
   229  
   230  	if !s1.vault.Running() {
   231  		t.Fatalf("Vault client should be running")
   232  	}
   233  }
   234  
   235  func connectionReset(msg string) bool {
   236  	return strings.Contains(msg, "EOF") || strings.Contains(msg, "connection reset by peer")
   237  }
   238  
   239  // Tests that the server will successfully reload its network connections,
   240  // upgrading from plaintext to TLS if the server's TLS configuration changes.
   241  func TestServer_Reload_TLSConnections_PlaintextToTLS(t *testing.T) {
   242  	t.Parallel()
   243  	assert := assert.New(t)
   244  
   245  	const (
   246  		cafile  = "../helper/tlsutil/testdata/ca.pem"
   247  		foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
   248  		fookey  = "../helper/tlsutil/testdata/nomad-foo-key.pem"
   249  	)
   250  	dir := tmpDir(t)
   251  	defer os.RemoveAll(dir)
   252  
   253  	s1 := TestServer(t, func(c *Config) {
   254  		c.DataDir = path.Join(dir, "nodeA")
   255  	})
   256  	defer s1.Shutdown()
   257  
   258  	// assert that the server started in plaintext mode
   259  	assert.Equal(s1.config.TLSConfig.CertFile, "")
   260  
   261  	newTLSConfig := &config.TLSConfig{
   262  		EnableHTTP:           true,
   263  		EnableRPC:            true,
   264  		VerifyServerHostname: true,
   265  		CAFile:               cafile,
   266  		CertFile:             foocert,
   267  		KeyFile:              fookey,
   268  	}
   269  
   270  	err := s1.reloadTLSConnections(newTLSConfig)
   271  	assert.Nil(err)
   272  	assert.True(s1.config.TLSConfig.CertificateInfoIsEqual(newTLSConfig))
   273  
   274  	codec := rpcClient(t, s1)
   275  
   276  	node := mock.Node()
   277  	req := &structs.NodeRegisterRequest{
   278  		Node:         node,
   279  		WriteRequest: structs.WriteRequest{Region: "global"},
   280  	}
   281  
   282  	var resp structs.GenericResponse
   283  	err = msgpackrpc.CallWithCodec(codec, "Node.Register", req, &resp)
   284  	assert.NotNil(err)
   285  	assert.True(connectionReset(err.Error()))
   286  }
   287  
   288  // Tests that the server will successfully reload its network connections,
   289  // downgrading from TLS to plaintext if the server's TLS configuration changes.
   290  func TestServer_Reload_TLSConnections_TLSToPlaintext_RPC(t *testing.T) {
   291  	t.Parallel()
   292  	assert := assert.New(t)
   293  
   294  	const (
   295  		cafile  = "../helper/tlsutil/testdata/ca.pem"
   296  		foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
   297  		fookey  = "../helper/tlsutil/testdata/nomad-foo-key.pem"
   298  	)
   299  
   300  	dir := tmpDir(t)
   301  	defer os.RemoveAll(dir)
   302  
   303  	s1 := TestServer(t, func(c *Config) {
   304  		c.DataDir = path.Join(dir, "nodeB")
   305  		c.TLSConfig = &config.TLSConfig{
   306  			EnableHTTP:           true,
   307  			EnableRPC:            true,
   308  			VerifyServerHostname: true,
   309  			CAFile:               cafile,
   310  			CertFile:             foocert,
   311  			KeyFile:              fookey,
   312  		}
   313  	})
   314  	defer s1.Shutdown()
   315  
   316  	newTLSConfig := &config.TLSConfig{}
   317  
   318  	err := s1.reloadTLSConnections(newTLSConfig)
   319  	assert.Nil(err)
   320  	assert.True(s1.config.TLSConfig.CertificateInfoIsEqual(newTLSConfig))
   321  
   322  	codec := rpcClient(t, s1)
   323  
   324  	node := mock.Node()
   325  	req := &structs.NodeRegisterRequest{
   326  		Node:         node,
   327  		WriteRequest: structs.WriteRequest{Region: "global"},
   328  	}
   329  
   330  	var resp structs.GenericResponse
   331  	err = msgpackrpc.CallWithCodec(codec, "Node.Register", req, &resp)
   332  	assert.Nil(err)
   333  }
   334  
   335  // Tests that the server will successfully reload its network connections,
   336  // downgrading only RPC connections
   337  func TestServer_Reload_TLSConnections_TLSToPlaintext_OnlyRPC(t *testing.T) {
   338  	t.Parallel()
   339  	assert := assert.New(t)
   340  
   341  	const (
   342  		cafile  = "../helper/tlsutil/testdata/ca.pem"
   343  		foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
   344  		fookey  = "../helper/tlsutil/testdata/nomad-foo-key.pem"
   345  	)
   346  
   347  	dir := tmpDir(t)
   348  	defer os.RemoveAll(dir)
   349  
   350  	s1 := TestServer(t, func(c *Config) {
   351  		c.DataDir = path.Join(dir, "nodeB")
   352  		c.TLSConfig = &config.TLSConfig{
   353  			EnableHTTP:           true,
   354  			EnableRPC:            true,
   355  			VerifyServerHostname: true,
   356  			CAFile:               cafile,
   357  			CertFile:             foocert,
   358  			KeyFile:              fookey,
   359  		}
   360  	})
   361  	defer s1.Shutdown()
   362  
   363  	newTLSConfig := &config.TLSConfig{
   364  		EnableHTTP:           true,
   365  		EnableRPC:            false,
   366  		VerifyServerHostname: true,
   367  		CAFile:               cafile,
   368  		CertFile:             foocert,
   369  		KeyFile:              fookey,
   370  	}
   371  
   372  	err := s1.reloadTLSConnections(newTLSConfig)
   373  	assert.Nil(err)
   374  	assert.True(s1.config.TLSConfig.CertificateInfoIsEqual(newTLSConfig))
   375  
   376  	codec := rpcClient(t, s1)
   377  
   378  	node := mock.Node()
   379  	req := &structs.NodeRegisterRequest{
   380  		Node:         node,
   381  		WriteRequest: structs.WriteRequest{Region: "global"},
   382  	}
   383  
   384  	var resp structs.GenericResponse
   385  	err = msgpackrpc.CallWithCodec(codec, "Node.Register", req, &resp)
   386  	assert.Nil(err)
   387  }
   388  
   389  // Tests that the server will successfully reload its network connections,
   390  // upgrading only RPC connections
   391  func TestServer_Reload_TLSConnections_PlaintextToTLS_OnlyRPC(t *testing.T) {
   392  	t.Parallel()
   393  	assert := assert.New(t)
   394  
   395  	const (
   396  		cafile  = "../helper/tlsutil/testdata/ca.pem"
   397  		foocert = "../helper/tlsutil/testdata/nomad-foo.pem"
   398  		fookey  = "../helper/tlsutil/testdata/nomad-foo-key.pem"
   399  	)
   400  
   401  	dir := tmpDir(t)
   402  	defer os.RemoveAll(dir)
   403  
   404  	s1 := TestServer(t, func(c *Config) {
   405  		c.DataDir = path.Join(dir, "nodeB")
   406  		c.TLSConfig = &config.TLSConfig{
   407  			EnableHTTP:           true,
   408  			EnableRPC:            false,
   409  			VerifyServerHostname: true,
   410  			CAFile:               cafile,
   411  			CertFile:             foocert,
   412  			KeyFile:              fookey,
   413  		}
   414  	})
   415  	defer s1.Shutdown()
   416  
   417  	newTLSConfig := &config.TLSConfig{
   418  		EnableHTTP:           true,
   419  		EnableRPC:            true,
   420  		VerifyServerHostname: true,
   421  		CAFile:               cafile,
   422  		CertFile:             foocert,
   423  		KeyFile:              fookey,
   424  	}
   425  
   426  	err := s1.reloadTLSConnections(newTLSConfig)
   427  	assert.Nil(err)
   428  	assert.True(s1.config.TLSConfig.EnableRPC)
   429  	assert.True(s1.config.TLSConfig.CertificateInfoIsEqual(newTLSConfig))
   430  
   431  	codec := rpcClient(t, s1)
   432  
   433  	node := mock.Node()
   434  	req := &structs.NodeRegisterRequest{
   435  		Node:         node,
   436  		WriteRequest: structs.WriteRequest{Region: "global"},
   437  	}
   438  
   439  	var resp structs.GenericResponse
   440  	err = msgpackrpc.CallWithCodec(codec, "Node.Register", req, &resp)
   441  	assert.NotNil(err)
   442  	assert.True(connectionReset(err.Error()))
   443  }
   444  
   445  // Test that Raft connections are reloaded as expected when a Nomad server is
   446  // upgraded from plaintext to TLS
   447  func TestServer_Reload_TLSConnections_Raft(t *testing.T) {
   448  	assert := assert.New(t)
   449  	t.Parallel()
   450  	const (
   451  		cafile  = "../../helper/tlsutil/testdata/ca.pem"
   452  		foocert = "../../helper/tlsutil/testdata/nomad-foo.pem"
   453  		fookey  = "../../helper/tlsutil/testdata/nomad-foo-key.pem"
   454  		barcert = "../dev/tls_cluster/certs/nomad.pem"
   455  		barkey  = "../dev/tls_cluster/certs/nomad-key.pem"
   456  	)
   457  	dir := tmpDir(t)
   458  	defer os.RemoveAll(dir)
   459  
   460  	s1 := TestServer(t, func(c *Config) {
   461  		c.BootstrapExpect = 2
   462  		c.DevMode = false
   463  		c.DevDisableBootstrap = true
   464  		c.DataDir = path.Join(dir, "node1")
   465  		c.NodeName = "node1"
   466  		c.Region = "regionFoo"
   467  	})
   468  	defer s1.Shutdown()
   469  
   470  	s2 := TestServer(t, func(c *Config) {
   471  		c.BootstrapExpect = 2
   472  		c.DevMode = false
   473  		c.DevDisableBootstrap = true
   474  		c.DataDir = path.Join(dir, "node2")
   475  		c.NodeName = "node2"
   476  		c.Region = "regionFoo"
   477  	})
   478  	defer s2.Shutdown()
   479  
   480  	TestJoin(t, s1, s2)
   481  	servers := []*Server{s1, s2}
   482  
   483  	testutil.WaitForLeader(t, s1.RPC)
   484  
   485  	newTLSConfig := &config.TLSConfig{
   486  		EnableHTTP:        true,
   487  		VerifyHTTPSClient: true,
   488  		CAFile:            cafile,
   489  		CertFile:          foocert,
   490  		KeyFile:           fookey,
   491  	}
   492  
   493  	err := s1.reloadTLSConnections(newTLSConfig)
   494  	assert.Nil(err)
   495  
   496  	{
   497  		for _, serv := range servers {
   498  			testutil.WaitForResult(func() (bool, error) {
   499  				args := &structs.GenericRequest{}
   500  				var leader string
   501  				err := serv.RPC("Status.Leader", args, &leader)
   502  				if leader != "" && err != nil {
   503  					return false, fmt.Errorf("Should not have found leader but got %s", leader)
   504  				}
   505  				return true, nil
   506  			}, func(err error) {
   507  				t.Fatalf("err: %v", err)
   508  			})
   509  		}
   510  	}
   511  
   512  	secondNewTLSConfig := &config.TLSConfig{
   513  		EnableHTTP:        true,
   514  		VerifyHTTPSClient: true,
   515  		CAFile:            cafile,
   516  		CertFile:          barcert,
   517  		KeyFile:           barkey,
   518  	}
   519  
   520  	// Now, transition the other server to TLS, which should restore their
   521  	// ability to communicate.
   522  	err = s2.reloadTLSConnections(secondNewTLSConfig)
   523  	assert.Nil(err)
   524  
   525  	testutil.WaitForLeader(t, s2.RPC)
   526  }
   527  
   528  func TestServer_InvalidSchedulers(t *testing.T) {
   529  	t.Parallel()
   530  	require := require.New(t)
   531  
   532  	// Set the config to not have the core scheduler
   533  	config := DefaultConfig()
   534  	logger := testlog.HCLogger(t)
   535  	s := &Server{
   536  		config: config,
   537  		logger: logger,
   538  	}
   539  
   540  	config.EnabledSchedulers = []string{"batch"}
   541  	err := s.setupWorkers()
   542  	require.NotNil(err)
   543  	require.Contains(err.Error(), "scheduler not enabled")
   544  
   545  	// Set the config to have an unknown scheduler
   546  	config.EnabledSchedulers = []string{"batch", structs.JobTypeCore, "foo"}
   547  	err = s.setupWorkers()
   548  	require.NotNil(err)
   549  	require.Contains(err.Error(), "foo")
   550  }