github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/client/client_test.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hashicorp/nomad/client/config"
    16  	"github.com/hashicorp/nomad/nomad"
    17  	"github.com/hashicorp/nomad/nomad/mock"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	"github.com/hashicorp/nomad/testutil"
    20  
    21  	ctestutil "github.com/hashicorp/nomad/client/testutil"
    22  )
    23  
    24  var nextPort uint32 = 16000
    25  
    26  func getPort() int {
    27  	return int(atomic.AddUint32(&nextPort, 1))
    28  }
    29  
    30  func testServer(t *testing.T, cb func(*nomad.Config)) (*nomad.Server, string) {
    31  	// Setup the default settings
    32  	config := nomad.DefaultConfig()
    33  	config.Build = "unittest"
    34  	config.DevMode = true
    35  	config.RPCAddr = &net.TCPAddr{
    36  		IP:   []byte{127, 0, 0, 1},
    37  		Port: getPort(),
    38  	}
    39  	config.NodeName = fmt.Sprintf("Node %d", config.RPCAddr.Port)
    40  
    41  	// Tighten the Serf timing
    42  	config.SerfConfig.MemberlistConfig.BindAddr = "127.0.0.1"
    43  	config.SerfConfig.MemberlistConfig.BindPort = getPort()
    44  	config.SerfConfig.MemberlistConfig.SuspicionMult = 2
    45  	config.SerfConfig.MemberlistConfig.RetransmitMult = 2
    46  	config.SerfConfig.MemberlistConfig.ProbeTimeout = 50 * time.Millisecond
    47  	config.SerfConfig.MemberlistConfig.ProbeInterval = 100 * time.Millisecond
    48  	config.SerfConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond
    49  
    50  	// Tighten the Raft timing
    51  	config.RaftConfig.LeaderLeaseTimeout = 20 * time.Millisecond
    52  	config.RaftConfig.HeartbeatTimeout = 40 * time.Millisecond
    53  	config.RaftConfig.ElectionTimeout = 40 * time.Millisecond
    54  	config.RaftConfig.StartAsLeader = true
    55  	config.RaftTimeout = 500 * time.Millisecond
    56  
    57  	// Invoke the callback if any
    58  	if cb != nil {
    59  		cb(config)
    60  	}
    61  
    62  	// Create server
    63  	server, err := nomad.NewServer(config)
    64  	if err != nil {
    65  		t.Fatalf("err: %v", err)
    66  	}
    67  	return server, config.RPCAddr.String()
    68  }
    69  
    70  func testClient(t *testing.T, cb func(c *config.Config)) *Client {
    71  	conf := DefaultConfig()
    72  	if cb != nil {
    73  		cb(conf)
    74  	}
    75  	conf.DevMode = true
    76  
    77  	client, err := NewClient(conf)
    78  	if err != nil {
    79  		t.Fatalf("err: %v", err)
    80  	}
    81  	return client
    82  }
    83  
    84  func TestClient_StartStop(t *testing.T) {
    85  	client := testClient(t, nil)
    86  	if err := client.Shutdown(); err != nil {
    87  		t.Fatalf("err: %v", err)
    88  	}
    89  }
    90  
    91  func TestClient_RPC(t *testing.T) {
    92  	s1, addr := testServer(t, nil)
    93  	defer s1.Shutdown()
    94  
    95  	c1 := testClient(t, func(c *config.Config) {
    96  		c.Servers = []string{addr}
    97  	})
    98  	defer c1.Shutdown()
    99  
   100  	// RPC should succeed
   101  	testutil.WaitForResult(func() (bool, error) {
   102  		var out struct{}
   103  		err := c1.RPC("Status.Ping", struct{}{}, &out)
   104  		return err == nil, err
   105  	}, func(err error) {
   106  		t.Fatalf("err: %v", err)
   107  	})
   108  }
   109  
   110  func TestClient_RPC_Passthrough(t *testing.T) {
   111  	s1, _ := testServer(t, nil)
   112  	defer s1.Shutdown()
   113  
   114  	c1 := testClient(t, func(c *config.Config) {
   115  		c.RPCHandler = s1
   116  	})
   117  	defer c1.Shutdown()
   118  
   119  	// RPC should succeed
   120  	testutil.WaitForResult(func() (bool, error) {
   121  		var out struct{}
   122  		err := c1.RPC("Status.Ping", struct{}{}, &out)
   123  		return err == nil, err
   124  	}, func(err error) {
   125  		t.Fatalf("err: %v", err)
   126  	})
   127  }
   128  
   129  func TestClient_Fingerprint(t *testing.T) {
   130  	c := testClient(t, nil)
   131  	defer c.Shutdown()
   132  
   133  	// Ensure kernel and arch are always present
   134  	node := c.Node()
   135  	if node.Attributes["kernel.name"] == "" {
   136  		t.Fatalf("missing kernel.name")
   137  	}
   138  	if node.Attributes["arch"] == "" {
   139  		t.Fatalf("missing arch")
   140  	}
   141  }
   142  
   143  func TestClient_Drivers(t *testing.T) {
   144  	ctestutil.ExecCompatible(t)
   145  	c := testClient(t, nil)
   146  	defer c.Shutdown()
   147  
   148  	node := c.Node()
   149  	if node.Attributes["driver.exec"] == "" {
   150  		t.Fatalf("missing exec driver")
   151  	}
   152  }
   153  
   154  func TestClient_Register(t *testing.T) {
   155  	s1, _ := testServer(t, nil)
   156  	defer s1.Shutdown()
   157  	testutil.WaitForLeader(t, s1.RPC)
   158  
   159  	c1 := testClient(t, func(c *config.Config) {
   160  		c.RPCHandler = s1
   161  	})
   162  	defer c1.Shutdown()
   163  
   164  	req := structs.NodeSpecificRequest{
   165  		NodeID:       c1.Node().ID,
   166  		QueryOptions: structs.QueryOptions{Region: "global"},
   167  	}
   168  	var out structs.SingleNodeResponse
   169  
   170  	// Register should succeed
   171  	testutil.WaitForResult(func() (bool, error) {
   172  		err := s1.RPC("Node.GetNode", &req, &out)
   173  		if err != nil {
   174  			return false, err
   175  		}
   176  		if out.Node == nil {
   177  			return false, fmt.Errorf("missing reg")
   178  		}
   179  		return out.Node.ID == req.NodeID, nil
   180  	}, func(err error) {
   181  		t.Fatalf("err: %v", err)
   182  	})
   183  }
   184  
   185  func TestClient_Heartbeat(t *testing.T) {
   186  	s1, _ := testServer(t, func(c *nomad.Config) {
   187  		c.MinHeartbeatTTL = 50 * time.Millisecond
   188  	})
   189  	defer s1.Shutdown()
   190  	testutil.WaitForLeader(t, s1.RPC)
   191  
   192  	c1 := testClient(t, func(c *config.Config) {
   193  		c.RPCHandler = s1
   194  	})
   195  	defer c1.Shutdown()
   196  
   197  	req := structs.NodeSpecificRequest{
   198  		NodeID:       c1.Node().ID,
   199  		QueryOptions: structs.QueryOptions{Region: "global"},
   200  	}
   201  	var out structs.SingleNodeResponse
   202  
   203  	// Register should succeed
   204  	testutil.WaitForResult(func() (bool, error) {
   205  		err := s1.RPC("Node.GetNode", &req, &out)
   206  		if err != nil {
   207  			return false, err
   208  		}
   209  		if out.Node == nil {
   210  			return false, fmt.Errorf("missing reg")
   211  		}
   212  		return out.Node.Status == structs.NodeStatusReady, nil
   213  	}, func(err error) {
   214  		t.Fatalf("err: %v", err)
   215  	})
   216  }
   217  
   218  func TestClient_UpdateAllocStatus(t *testing.T) {
   219  	s1, _ := testServer(t, nil)
   220  	defer s1.Shutdown()
   221  	testutil.WaitForLeader(t, s1.RPC)
   222  
   223  	c1 := testClient(t, func(c *config.Config) {
   224  		c.RPCHandler = s1
   225  	})
   226  	defer c1.Shutdown()
   227  
   228  	alloc := mock.Alloc()
   229  	alloc.NodeID = c1.Node().ID
   230  
   231  	state := s1.State()
   232  	state.UpsertAllocs(100, []*structs.Allocation{alloc})
   233  
   234  	newAlloc := new(structs.Allocation)
   235  	*newAlloc = *alloc
   236  	newAlloc.ClientStatus = structs.AllocClientStatusRunning
   237  
   238  	err := c1.updateAllocStatus(newAlloc)
   239  	if err != nil {
   240  		t.Fatalf("err: %v", err)
   241  	}
   242  
   243  	out, err := state.AllocByID(alloc.ID)
   244  	if err != nil {
   245  		t.Fatalf("err: %v", err)
   246  	}
   247  
   248  	if out == nil || out.ClientStatus != structs.AllocClientStatusRunning {
   249  		t.Fatalf("bad: %#v", out)
   250  	}
   251  }
   252  
   253  func TestClient_WatchAllocs(t *testing.T) {
   254  	ctestutil.ExecCompatible(t)
   255  	s1, _ := testServer(t, nil)
   256  	defer s1.Shutdown()
   257  	testutil.WaitForLeader(t, s1.RPC)
   258  
   259  	c1 := testClient(t, func(c *config.Config) {
   260  		c.RPCHandler = s1
   261  	})
   262  	defer c1.Shutdown()
   263  
   264  	// Create mock allocations
   265  	alloc1 := mock.Alloc()
   266  	alloc1.NodeID = c1.Node().ID
   267  	alloc2 := mock.Alloc()
   268  	alloc2.NodeID = c1.Node().ID
   269  
   270  	state := s1.State()
   271  	err := state.UpsertAllocs(100,
   272  		[]*structs.Allocation{alloc1, alloc2})
   273  	if err != nil {
   274  		t.Fatalf("err: %v", err)
   275  	}
   276  
   277  	// Both allocations should get registered
   278  	testutil.WaitForResult(func() (bool, error) {
   279  		c1.allocLock.RLock()
   280  		num := len(c1.allocs)
   281  		c1.allocLock.RUnlock()
   282  		return num == 2, nil
   283  	}, func(err error) {
   284  		t.Fatalf("err: %v", err)
   285  	})
   286  
   287  	// Delete one allocation
   288  	err = state.DeleteEval(101, nil, []string{alloc1.ID})
   289  	if err != nil {
   290  		t.Fatalf("err: %v", err)
   291  	}
   292  
   293  	// Update the other allocation
   294  	alloc2.DesiredStatus = structs.AllocDesiredStatusStop
   295  	err = state.UpsertAllocs(102,
   296  		[]*structs.Allocation{alloc2})
   297  	if err != nil {
   298  		t.Fatalf("err: %v", err)
   299  	}
   300  
   301  	// One allocations should get de-registered
   302  	testutil.WaitForResult(func() (bool, error) {
   303  		c1.allocLock.RLock()
   304  		num := len(c1.allocs)
   305  		c1.allocLock.RUnlock()
   306  		return num == 1, nil
   307  	}, func(err error) {
   308  		t.Fatalf("err: %v", err)
   309  	})
   310  
   311  	// One allocations should get updated
   312  	testutil.WaitForResult(func() (bool, error) {
   313  		c1.allocLock.RLock()
   314  		ar := c1.allocs[alloc2.ID]
   315  		c1.allocLock.RUnlock()
   316  		return ar.Alloc().DesiredStatus == structs.AllocDesiredStatusStop, nil
   317  	}, func(err error) {
   318  		t.Fatalf("err: %v", err)
   319  	})
   320  }
   321  
   322  /*
   323  TODO: This test is disabled til a follow-up api changes the restore state interface.
   324  The driver/executor interface will be changed from Open to Cleanup, in which
   325  clean-up tears down previous allocs.
   326  
   327  func TestClient_SaveRestoreState(t *testing.T) {
   328  	ctestutil.ExecCompatible(t)
   329  	s1, _ := testServer(t, nil)
   330  	defer s1.Shutdown()
   331  	testutil.WaitForLeader(t, s1.RPC)
   332  
   333  	c1 := testClient(t, func(c *config.Config) {
   334  		c.RPCHandler = s1
   335  	})
   336  	defer c1.Shutdown()
   337  
   338  	// Create mock allocations
   339  	alloc1 := mock.Alloc()
   340  	alloc1.NodeID = c1.Node().ID
   341  	task := alloc1.Job.TaskGroups[0].Tasks[0]
   342  	task.Config["command"] = "/bin/sleep"
   343  	task.Config["args"] = "10"
   344  
   345  	state := s1.State()
   346  	err := state.UpsertAllocs(100,
   347  		[]*structs.Allocation{alloc1})
   348  	if err != nil {
   349  		t.Fatalf("err: %v", err)
   350  	}
   351  
   352  	// Allocations should get registered
   353  	testutil.WaitForResult(func() (bool, error) {
   354  		c1.allocLock.RLock()
   355  		num := len(c1.allocs)
   356  		c1.allocLock.RUnlock()
   357  		return num == 1, nil
   358  	}, func(err error) {
   359  		t.Fatalf("err: %v", err)
   360  	})
   361  
   362  	// Shutdown the client, saves state
   363  	err = c1.Shutdown()
   364  	if err != nil {
   365  		t.Fatalf("err: %v", err)
   366  	}
   367  
   368  	// Create a new client
   369  	c2, err := NewClient(c1.config)
   370  	if err != nil {
   371  		t.Fatalf("err: %v", err)
   372  	}
   373  	defer c2.Shutdown()
   374  
   375  	// Ensure the allocation is running
   376  	c2.allocLock.RLock()
   377  	ar := c1.allocs[alloc1.ID]
   378  	c2.allocLock.RUnlock()
   379  	if ar.Alloc().ClientStatus != structs.AllocClientStatusRunning {
   380  		t.Fatalf("bad: %#v", ar.Alloc())
   381  	}
   382  }
   383  */
   384  
   385  func TestClient_Init(t *testing.T) {
   386  	dir, err := ioutil.TempDir("", "nomad")
   387  	if err != nil {
   388  		t.Fatalf("err: %s", err)
   389  	}
   390  	defer os.RemoveAll(dir)
   391  	allocDir := filepath.Join(dir, "alloc")
   392  
   393  	client := &Client{
   394  		config: &config.Config{
   395  			AllocDir: allocDir,
   396  		},
   397  		logger: log.New(os.Stderr, "", log.LstdFlags),
   398  	}
   399  	if err := client.init(); err != nil {
   400  		t.Fatalf("err: %s", err)
   401  	}
   402  
   403  	if _, err := os.Stat(allocDir); err != nil {
   404  		t.Fatalf("err: %s", err)
   405  	}
   406  }
   407  
   408  func TestClient_SetServers(t *testing.T) {
   409  	client := testClient(t, nil)
   410  
   411  	// Sets an empty list
   412  	client.SetServers(nil)
   413  	if client.servers == nil {
   414  		t.Fatalf("should not be nil")
   415  	}
   416  
   417  	// Set the initial servers list
   418  	expect := []string{"foo"}
   419  	client.SetServers(expect)
   420  	if !reflect.DeepEqual(client.servers, expect) {
   421  		t.Fatalf("expect %v, got %v", expect, client.servers)
   422  	}
   423  
   424  	// Add a server
   425  	expect = []string{"foo", "bar"}
   426  	client.SetServers(expect)
   427  	if !reflect.DeepEqual(client.servers, expect) {
   428  		t.Fatalf("expect %v, got %v", expect, client.servers)
   429  	}
   430  
   431  	// Remove a server
   432  	expect = []string{"bar"}
   433  	client.SetServers(expect)
   434  	if !reflect.DeepEqual(client.servers, expect) {
   435  		t.Fatalf("expect %v, got %v", expect, client.servers)
   436  	}
   437  
   438  	// Add and remove a server
   439  	expect = []string{"baz", "zip"}
   440  	client.SetServers(expect)
   441  	if !reflect.DeepEqual(client.servers, expect) {
   442  		t.Fatalf("expect %v, got %v", expect, client.servers)
   443  	}
   444  
   445  	// Query the servers list
   446  	if servers := client.Servers(); !reflect.DeepEqual(servers, expect) {
   447  		t.Fatalf("expect %v, got %v", expect, servers)
   448  	}
   449  }