github.com/bigcommerce/nomad@v0.9.3-bc/client/state/db_test.go (about)

     1  package state
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"reflect"
     7  	"testing"
     8  
     9  	trstate "github.com/hashicorp/nomad/client/allocrunner/taskrunner/state"
    10  	dmstate "github.com/hashicorp/nomad/client/devicemanager/state"
    11  	driverstate "github.com/hashicorp/nomad/client/pluginmanager/drivermanager/state"
    12  	"github.com/hashicorp/nomad/helper/testlog"
    13  	"github.com/hashicorp/nomad/nomad/mock"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/kr/pretty"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func setupBoltStateDB(t *testing.T) (*BoltStateDB, func()) {
    20  	dir, err := ioutil.TempDir("", "nomadtest")
    21  	require.NoError(t, err)
    22  
    23  	db, err := NewBoltStateDB(testlog.HCLogger(t), dir)
    24  	if err != nil {
    25  		if err := os.RemoveAll(dir); err != nil {
    26  			t.Logf("error removing boltdb dir: %v", err)
    27  		}
    28  		t.Fatalf("error creating boltdb: %v", err)
    29  	}
    30  
    31  	cleanup := func() {
    32  		if err := db.Close(); err != nil {
    33  			t.Errorf("error closing boltdb: %v", err)
    34  		}
    35  		if err := os.RemoveAll(dir); err != nil {
    36  			t.Logf("error removing boltdb dir: %v", err)
    37  		}
    38  	}
    39  
    40  	return db.(*BoltStateDB), cleanup
    41  }
    42  
    43  func testDB(t *testing.T, f func(*testing.T, StateDB)) {
    44  	boltdb, cleanup := setupBoltStateDB(t)
    45  	defer cleanup()
    46  
    47  	memdb := NewMemDB(testlog.HCLogger(t))
    48  
    49  	impls := []StateDB{boltdb, memdb}
    50  
    51  	for _, db := range impls {
    52  		db := db
    53  		t.Run(db.Name(), func(t *testing.T) {
    54  			f(t, db)
    55  		})
    56  	}
    57  }
    58  
    59  // TestStateDB asserts the behavior of GetAllAllocations, PutAllocation, and
    60  // DeleteAllocationBucket for all operational StateDB implementations.
    61  func TestStateDB_Allocations(t *testing.T) {
    62  	t.Parallel()
    63  
    64  	testDB(t, func(t *testing.T, db StateDB) {
    65  		require := require.New(t)
    66  
    67  		// Empty database should return empty non-nil results
    68  		allocs, errs, err := db.GetAllAllocations()
    69  		require.NoError(err)
    70  		require.NotNil(allocs)
    71  		require.Empty(allocs)
    72  		require.NotNil(errs)
    73  		require.Empty(errs)
    74  
    75  		// Put allocations
    76  		alloc1 := mock.Alloc()
    77  		alloc2 := mock.BatchAlloc()
    78  
    79  		//XXX Sadly roundtripping allocs loses time.Duration type
    80  		//    information from the Config map[string]interface{}. As
    81  		//    the mock driver itself with unmarshal run_for into the
    82  		//    proper type, we can safely ignore it here.
    83  		delete(alloc2.Job.TaskGroups[0].Tasks[0].Config, "run_for")
    84  
    85  		require.NoError(db.PutAllocation(alloc1))
    86  		require.NoError(db.PutAllocation(alloc2))
    87  
    88  		// Retrieve them
    89  		allocs, errs, err = db.GetAllAllocations()
    90  		require.NoError(err)
    91  		require.NotNil(allocs)
    92  		require.Len(allocs, 2)
    93  		for _, a := range allocs {
    94  			switch a.ID {
    95  			case alloc1.ID:
    96  				if !reflect.DeepEqual(a, alloc1) {
    97  					pretty.Ldiff(t, a, alloc1)
    98  					t.Fatalf("alloc %q unequal", a.ID)
    99  				}
   100  			case alloc2.ID:
   101  				if !reflect.DeepEqual(a, alloc2) {
   102  					pretty.Ldiff(t, a, alloc2)
   103  					t.Fatalf("alloc %q unequal", a.ID)
   104  				}
   105  			default:
   106  				t.Fatalf("unexpected alloc id %q", a.ID)
   107  			}
   108  		}
   109  		require.NotNil(errs)
   110  		require.Empty(errs)
   111  
   112  		// Add another
   113  		alloc3 := mock.SystemAlloc()
   114  		require.NoError(db.PutAllocation(alloc3))
   115  		allocs, errs, err = db.GetAllAllocations()
   116  		require.NoError(err)
   117  		require.NotNil(allocs)
   118  		require.Len(allocs, 3)
   119  		require.Contains(allocs, alloc1)
   120  		require.Contains(allocs, alloc2)
   121  		require.Contains(allocs, alloc3)
   122  		require.NotNil(errs)
   123  		require.Empty(errs)
   124  
   125  		// Deleting a nonexistent alloc is a noop
   126  		require.NoError(db.DeleteAllocationBucket("asdf"))
   127  		allocs, _, err = db.GetAllAllocations()
   128  		require.NoError(err)
   129  		require.NotNil(allocs)
   130  		require.Len(allocs, 3)
   131  
   132  		// Delete alloc1
   133  		require.NoError(db.DeleteAllocationBucket(alloc1.ID))
   134  		allocs, errs, err = db.GetAllAllocations()
   135  		require.NoError(err)
   136  		require.NotNil(allocs)
   137  		require.Len(allocs, 2)
   138  		require.Contains(allocs, alloc2)
   139  		require.Contains(allocs, alloc3)
   140  		require.NotNil(errs)
   141  		require.Empty(errs)
   142  	})
   143  }
   144  
   145  // TestStateDB_TaskState asserts the behavior of task state related StateDB
   146  // methods.
   147  func TestStateDB_TaskState(t *testing.T) {
   148  	t.Parallel()
   149  
   150  	testDB(t, func(t *testing.T, db StateDB) {
   151  		require := require.New(t)
   152  
   153  		// Getting nonexistent state should return nils
   154  		ls, ts, err := db.GetTaskRunnerState("allocid", "taskname")
   155  		require.NoError(err)
   156  		require.Nil(ls)
   157  		require.Nil(ts)
   158  
   159  		// Putting TaskState without first putting the allocation should work
   160  		state := structs.NewTaskState()
   161  		state.Failed = true // set a non-default value
   162  		require.NoError(db.PutTaskState("allocid", "taskname", state))
   163  
   164  		// Getting should return the available state
   165  		ls, ts, err = db.GetTaskRunnerState("allocid", "taskname")
   166  		require.NoError(err)
   167  		require.Nil(ls)
   168  		require.Equal(state, ts)
   169  
   170  		// Deleting a nonexistent task should not error
   171  		require.NoError(db.DeleteTaskBucket("adsf", "asdf"))
   172  		require.NoError(db.DeleteTaskBucket("asllocid", "asdf"))
   173  
   174  		// Data should be untouched
   175  		ls, ts, err = db.GetTaskRunnerState("allocid", "taskname")
   176  		require.NoError(err)
   177  		require.Nil(ls)
   178  		require.Equal(state, ts)
   179  
   180  		// Deleting the task should remove the state
   181  		require.NoError(db.DeleteTaskBucket("allocid", "taskname"))
   182  		ls, ts, err = db.GetTaskRunnerState("allocid", "taskname")
   183  		require.NoError(err)
   184  		require.Nil(ls)
   185  		require.Nil(ts)
   186  
   187  		// Putting LocalState should work just like TaskState
   188  		origLocalState := trstate.NewLocalState()
   189  		require.NoError(db.PutTaskRunnerLocalState("allocid", "taskname", origLocalState))
   190  		ls, ts, err = db.GetTaskRunnerState("allocid", "taskname")
   191  		require.NoError(err)
   192  		require.Equal(origLocalState, ls)
   193  		require.Nil(ts)
   194  	})
   195  }
   196  
   197  // TestStateDB_DeviceManager asserts the behavior of device manager state related StateDB
   198  // methods.
   199  func TestStateDB_DeviceManager(t *testing.T) {
   200  	t.Parallel()
   201  
   202  	testDB(t, func(t *testing.T, db StateDB) {
   203  		require := require.New(t)
   204  
   205  		// Getting nonexistent state should return nils
   206  		ps, err := db.GetDevicePluginState()
   207  		require.NoError(err)
   208  		require.Nil(ps)
   209  
   210  		// Putting PluginState should work
   211  		state := &dmstate.PluginState{}
   212  		require.NoError(db.PutDevicePluginState(state))
   213  
   214  		// Getting should return the available state
   215  		ps, err = db.GetDevicePluginState()
   216  		require.NoError(err)
   217  		require.NotNil(ps)
   218  		require.Equal(state, ps)
   219  	})
   220  }
   221  
   222  // TestStateDB_DriverManager asserts the behavior of device manager state related StateDB
   223  // methods.
   224  func TestStateDB_DriverManager(t *testing.T) {
   225  	t.Parallel()
   226  
   227  	testDB(t, func(t *testing.T, db StateDB) {
   228  		require := require.New(t)
   229  
   230  		// Getting nonexistent state should return nils
   231  		ps, err := db.GetDriverPluginState()
   232  		require.NoError(err)
   233  		require.Nil(ps)
   234  
   235  		// Putting PluginState should work
   236  		state := &driverstate.PluginState{}
   237  		require.NoError(db.PutDriverPluginState(state))
   238  
   239  		// Getting should return the available state
   240  		ps, err = db.GetDriverPluginState()
   241  		require.NoError(err)
   242  		require.NotNil(ps)
   243  		require.Equal(state, ps)
   244  	})
   245  }
   246  
   247  // TestStateDB_Upgrade asserts calling Upgrade on new databases always
   248  // succeeds.
   249  func TestStateDB_Upgrade(t *testing.T) {
   250  	t.Parallel()
   251  
   252  	testDB(t, func(t *testing.T, db StateDB) {
   253  		require.NoError(t, db.Upgrade())
   254  	})
   255  }