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 }