github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/state/upgrade_test.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "testing" 7 8 "github.com/hashicorp/nomad/ci" 9 "github.com/hashicorp/nomad/helper/boltdd" 10 "github.com/hashicorp/nomad/helper/testlog" 11 "github.com/hashicorp/nomad/helper/uuid" 12 "github.com/stretchr/testify/require" 13 "go.etcd.io/bbolt" 14 ) 15 16 func setupBoltDB(t *testing.T) *bbolt.DB { 17 dir := t.TempDir() 18 19 db, err := bbolt.Open(filepath.Join(dir, "state.db"), 0666, nil) 20 require.NoError(t, err) 21 22 t.Cleanup(func() { 23 require.NoError(t, db.Close()) 24 }) 25 26 return db 27 } 28 29 // TestUpgrade_NeedsUpgrade_New asserts new state dbs do not need upgrading. 30 func TestUpgrade_NeedsUpgrade_New(t *testing.T) { 31 ci.Parallel(t) 32 33 // Setting up a new StateDB should initialize it at the latest version. 34 db := setupBoltStateDB(t) 35 36 to09, to12, err := NeedsUpgrade(db.DB().BoltDB()) 37 require.NoError(t, err) 38 require.False(t, to09) 39 require.False(t, to12) 40 } 41 42 // TestUpgrade_NeedsUpgrade_Old asserts state dbs with just the alloctions 43 // bucket *do* need upgrading. 44 func TestUpgrade_NeedsUpgrade_Old(t *testing.T) { 45 ci.Parallel(t) 46 47 db := setupBoltDB(t) 48 49 // Create the allocations bucket which exists in both the old and 0.9 50 // schemas 51 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 52 _, err := tx.CreateBucket(allocationsBucketName) 53 return err 54 })) 55 56 to09, to12, err := NeedsUpgrade(db) 57 require.NoError(t, err) 58 require.True(t, to09) 59 require.True(t, to12) 60 61 // Adding meta should mark it as upgraded 62 require.NoError(t, db.Update(addMeta)) 63 64 to09, to12, err = NeedsUpgrade(db) 65 require.NoError(t, err) 66 require.False(t, to09) 67 require.False(t, to12) 68 } 69 70 // TestUpgrade_NeedsUpgrade_Error asserts that an error is returned from 71 // NeedsUpgrade if an invalid db version is found. This is a safety measure to 72 // prevent invalid and unintentional upgrades when downgrading Nomad. 73 func TestUpgrade_NeedsUpgrade_Error(t *testing.T) { 74 ci.Parallel(t) 75 76 cases := [][]byte{ 77 {'"', '2', '"'}, // wrong type 78 {'1'}, // wrong version (never existed) 79 {'4'}, // wrong version (future) 80 } 81 82 for _, tc := range cases { 83 tc := tc 84 t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) { 85 db := setupBoltDB(t) 86 87 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 88 bkt, err := tx.CreateBucketIfNotExists(metaBucketName) 89 require.NoError(t, err) 90 91 return bkt.Put(metaVersionKey, tc) 92 })) 93 94 _, _, err := NeedsUpgrade(db) 95 require.Error(t, err) 96 }) 97 } 98 } 99 100 // TestUpgrade_DeleteInvalidAllocs asserts invalid allocations are deleted 101 // during state upgades instead of failing the entire agent. 102 func TestUpgrade_DeleteInvalidAllocs_NoAlloc(t *testing.T) { 103 ci.Parallel(t) 104 105 bdb := setupBoltDB(t) 106 107 db := boltdd.New(bdb) 108 109 allocID := []byte(uuid.Generate()) 110 111 // Create an allocation bucket with no `alloc` key. This is an observed 112 // pre-0.9 state corruption that should result in the allocation being 113 // dropped while allowing the upgrade to continue. 114 require.NoError(t, db.Update(func(tx *boltdd.Tx) error { 115 parentBkt, err := tx.CreateBucket(allocationsBucketName) 116 if err != nil { 117 return err 118 } 119 120 _, err = parentBkt.CreateBucket(allocID) 121 return err 122 })) 123 124 // Perform the Upgrade 125 require.NoError(t, db.Update(func(tx *boltdd.Tx) error { 126 return UpgradeAllocs(testlog.HCLogger(t), tx) 127 })) 128 129 // Assert invalid allocation bucket was removed 130 require.NoError(t, db.View(func(tx *boltdd.Tx) error { 131 parentBkt := tx.Bucket(allocationsBucketName) 132 if parentBkt == nil { 133 return fmt.Errorf("parent allocations bucket should not have been removed") 134 } 135 136 if parentBkt.Bucket(allocID) != nil { 137 return fmt.Errorf("invalid alloc bucket should have been deleted") 138 } 139 140 return nil 141 })) 142 } 143 144 // TestUpgrade_DeleteInvalidTaskEntries asserts invalid entries under a task 145 // bucket are deleted. 146 func TestUpgrade_upgradeTaskBucket_InvalidEntries(t *testing.T) { 147 ci.Parallel(t) 148 149 db := setupBoltDB(t) 150 151 taskName := []byte("fake-task") 152 153 // Insert unexpected bucket, unexpected key, and missing simple-all 154 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 155 bkt, err := tx.CreateBucket(taskName) 156 if err != nil { 157 return err 158 } 159 160 _, err = bkt.CreateBucket([]byte("unexpectedBucket")) 161 if err != nil { 162 return err 163 } 164 165 return bkt.Put([]byte("unexepectedKey"), []byte{'x'}) 166 })) 167 168 require.NoError(t, db.Update(func(tx *bbolt.Tx) error { 169 bkt := tx.Bucket(taskName) 170 171 // upgradeTaskBucket should fail 172 state, err := upgradeTaskBucket(testlog.HCLogger(t), bkt) 173 require.Nil(t, state) 174 require.Error(t, err) 175 176 // Invalid entries should have been deleted 177 cur := bkt.Cursor() 178 for k, v := cur.First(); k != nil; k, v = cur.Next() { 179 t.Errorf("unexpected entry found: key=%q value=%q", k, v) 180 } 181 182 return nil 183 })) 184 }