github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/state/upgrade.go (about) 1 package state 2 3 import ( 4 "bytes" 5 "container/list" 6 "fmt" 7 "os" 8 9 hclog "github.com/hashicorp/go-hclog" 10 "github.com/hashicorp/go-msgpack/codec" 11 "github.com/hashicorp/nomad/client/dynamicplugins" 12 "github.com/hashicorp/nomad/helper/boltdd" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "go.etcd.io/bbolt" 15 ) 16 17 // NeedsUpgrade returns true if the BoltDB needs upgrading or false if it is 18 // already up to date. 19 func NeedsUpgrade(bdb *bbolt.DB) (upgradeTo09, upgradeTo13 bool, err error) { 20 upgradeTo09 = true 21 upgradeTo13 = true 22 err = bdb.View(func(tx *bbolt.Tx) error { 23 b := tx.Bucket(metaBucketName) 24 if b == nil { 25 // No meta bucket; upgrade 26 return nil 27 } 28 29 v := b.Get(metaVersionKey) 30 if len(v) == 0 { 31 // No version; upgrade 32 return nil 33 } 34 35 if bytes.Equal(v, []byte{'2'}) { 36 upgradeTo09 = false 37 return nil 38 } 39 if bytes.Equal(v, metaVersion) { 40 upgradeTo09 = false 41 upgradeTo13 = false 42 return nil 43 } 44 45 // Version exists but does not match. Abort. 46 return fmt.Errorf("incompatible state version. expected %q but found %q", 47 metaVersion, v) 48 49 }) 50 51 return 52 } 53 54 // addMeta adds version metadata to BoltDB to mark it as upgraded and 55 // should be run at the end of the upgrade transaction. 56 func addMeta(tx *bbolt.Tx) error { 57 // Create the meta bucket if it doesn't exist 58 bkt, err := tx.CreateBucketIfNotExists(metaBucketName) 59 if err != nil { 60 return err 61 } 62 return bkt.Put(metaVersionKey, metaVersion) 63 } 64 65 // backupDB backs up the existing state database prior to upgrade overwriting 66 // previous backups. 67 func backupDB(bdb *bbolt.DB, dst string) error { 68 fd, err := os.Create(dst) 69 if err != nil { 70 return err 71 } 72 73 return bdb.View(func(tx *bbolt.Tx) error { 74 if _, err := tx.WriteTo(fd); err != nil { 75 fd.Close() 76 return err 77 } 78 79 return fd.Close() 80 }) 81 } 82 83 // UpgradeAllocs upgrades the boltdb schema. Example 0.8 schema: 84 // 85 // allocations 86 // 15d83e8a-74a2-b4da-3f17-ed5c12895ea8 87 // echo 88 // simple-all (342 bytes) 89 // alloc (2827 bytes) 90 // alloc-dir (166 bytes) 91 // immutable (15 bytes) 92 // mutable (1294 bytes) 93 func UpgradeAllocs(logger hclog.Logger, tx *boltdd.Tx) error { 94 btx := tx.BoltTx() 95 allocationsBucket := btx.Bucket(allocationsBucketName) 96 if allocationsBucket == nil { 97 // No state! 98 return nil 99 } 100 101 // Gather alloc buckets and remove unexpected key/value pairs 102 allocBuckets := [][]byte{} 103 cur := allocationsBucket.Cursor() 104 for k, v := cur.First(); k != nil; k, v = cur.Next() { 105 if v != nil { 106 logger.Warn("deleting unexpected key in state db", 107 "key", string(k), "value_bytes", len(v), 108 ) 109 110 if err := cur.Delete(); err != nil { 111 return fmt.Errorf("error deleting unexpected key %q: %v", string(k), err) 112 } 113 continue 114 } 115 116 allocBuckets = append(allocBuckets, k) 117 } 118 119 for _, allocBucket := range allocBuckets { 120 allocID := string(allocBucket) 121 122 bkt := allocationsBucket.Bucket(allocBucket) 123 if bkt == nil { 124 // This should never happen as we just read the bucket. 125 return fmt.Errorf("unexpected bucket missing %q", allocID) 126 } 127 128 allocLogger := logger.With("alloc_id", allocID) 129 if err := upgradeAllocBucket(allocLogger, tx, bkt, allocID); err != nil { 130 // Log and drop invalid allocs 131 allocLogger.Error("dropping invalid allocation due to error while upgrading state", 132 "error", err, 133 ) 134 135 // If we can't delete the bucket something is seriously 136 // wrong, fail hard. 137 if err := allocationsBucket.DeleteBucket(allocBucket); err != nil { 138 return fmt.Errorf("error deleting invalid allocation state: %v", err) 139 } 140 } 141 } 142 143 return nil 144 } 145 146 // upgradeAllocBucket upgrades an alloc bucket. 147 func upgradeAllocBucket(logger hclog.Logger, tx *boltdd.Tx, bkt *bbolt.Bucket, allocID string) error { 148 allocFound := false 149 taskBuckets := [][]byte{} 150 cur := bkt.Cursor() 151 for k, v := cur.First(); k != nil; k, v = cur.Next() { 152 switch string(k) { 153 case "alloc": 154 // Alloc has not changed; leave it be 155 allocFound = true 156 case "alloc-dir": 157 // Drop alloc-dir entries as they're no longer needed. 158 cur.Delete() 159 case "immutable": 160 // Drop immutable state. Nothing from it needs to be 161 // upgraded. 162 cur.Delete() 163 case "mutable": 164 // Decode and upgrade 165 if err := upgradeOldAllocMutable(tx, allocID, v); err != nil { 166 return err 167 } 168 cur.Delete() 169 default: 170 if v != nil { 171 logger.Warn("deleting unexpected state entry for allocation", 172 "key", string(k), "value_bytes", len(v), 173 ) 174 175 if err := cur.Delete(); err != nil { 176 return err 177 } 178 179 continue 180 } 181 182 // Nested buckets are tasks 183 taskBuckets = append(taskBuckets, k) 184 } 185 } 186 187 // If the alloc entry was not found, abandon this allocation as the 188 // state has been corrupted. 189 if !allocFound { 190 return fmt.Errorf("alloc entry not found") 191 } 192 193 // Upgrade tasks 194 for _, taskBucket := range taskBuckets { 195 taskName := string(taskBucket) 196 taskLogger := logger.With("task_name", taskName) 197 198 taskBkt := bkt.Bucket(taskBucket) 199 if taskBkt == nil { 200 // This should never happen as we just read the bucket. 201 return fmt.Errorf("unexpected bucket missing %q", taskName) 202 } 203 204 oldState, err := upgradeTaskBucket(taskLogger, taskBkt) 205 if err != nil { 206 taskLogger.Warn("dropping invalid task due to error while upgrading state", 207 "error", err, 208 ) 209 210 // Delete the invalid task bucket and treat failures 211 // here as unrecoverable errors. 212 if err := bkt.DeleteBucket(taskBucket); err != nil { 213 return fmt.Errorf("error deleting invalid task state for task %q: %v", 214 taskName, err, 215 ) 216 } 217 continue 218 } 219 220 // Convert 0.8 task state to 0.9 task state 221 localTaskState, err := oldState.Upgrade(allocID, taskName) 222 if err != nil { 223 taskLogger.Warn("dropping invalid task due to error while upgrading state", 224 "error", err, 225 ) 226 227 // Delete the invalid task bucket and treat failures 228 // here as unrecoverable errors. 229 if err := bkt.DeleteBucket(taskBucket); err != nil { 230 return fmt.Errorf("error deleting invalid task state for task %q: %v", 231 taskName, err, 232 ) 233 } 234 continue 235 } 236 237 // Insert the new task state 238 if err := putTaskRunnerLocalStateImpl(tx, allocID, taskName, localTaskState); err != nil { 239 return err 240 } 241 242 // Delete the old task bucket 243 if err := bkt.DeleteBucket(taskBucket); err != nil { 244 return err 245 } 246 247 taskLogger.Trace("upgraded", "from", oldState.Version) 248 } 249 250 return nil 251 } 252 253 // upgradeTaskBucket iterates over keys in a task bucket, deleting invalid keys 254 // and returning the 0.8 version of the state. 255 func upgradeTaskBucket(logger hclog.Logger, bkt *bbolt.Bucket) (*taskRunnerState08, error) { 256 simpleFound := false 257 var trState taskRunnerState08 258 259 cur := bkt.Cursor() 260 for k, v := cur.First(); k != nil; k, v = cur.Next() { 261 if v == nil { 262 // value is nil: delete unexpected bucket 263 logger.Warn("deleting unexpected task state bucket", 264 "bucket", string(k), 265 ) 266 267 if err := bkt.DeleteBucket(k); err != nil { 268 return nil, fmt.Errorf("error deleting unexpected task bucket %q: %v", string(k), err) 269 } 270 continue 271 } 272 273 if !bytes.Equal(k, []byte("simple-all")) { 274 // value is non-nil: delete unexpected entry 275 logger.Warn("deleting unexpected task state entry", 276 "key", string(k), "value_bytes", len(v), 277 ) 278 279 if err := cur.Delete(); err != nil { 280 return nil, fmt.Errorf("error delting unexpected task key %q: %v", string(k), err) 281 } 282 continue 283 } 284 285 // Decode simple-all 286 simpleFound = true 287 if err := codec.NewDecoderBytes(v, structs.MsgpackHandle).Decode(&trState); err != nil { 288 return nil, fmt.Errorf("failed to decode task state from 'simple-all' entry: %v", err) 289 } 290 } 291 292 if !simpleFound { 293 return nil, fmt.Errorf("task state entry not found") 294 } 295 296 return &trState, nil 297 } 298 299 // upgradeOldAllocMutable upgrades Nomad 0.8 alloc runner state. 300 func upgradeOldAllocMutable(tx *boltdd.Tx, allocID string, oldBytes []byte) error { 301 var oldMutable allocRunnerMutableState08 302 err := codec.NewDecoderBytes(oldBytes, structs.MsgpackHandle).Decode(&oldMutable) 303 if err != nil { 304 return err 305 } 306 307 // Upgrade Deployment Status 308 if err := putDeploymentStatusImpl(tx, allocID, oldMutable.DeploymentStatus); err != nil { 309 return err 310 } 311 312 // Upgrade Task States 313 for taskName, taskState := range oldMutable.TaskStates { 314 if err := putTaskStateImpl(tx, allocID, taskName, taskState); err != nil { 315 return err 316 } 317 } 318 319 return nil 320 } 321 322 func UpgradeDynamicPluginRegistry(logger hclog.Logger, tx *boltdd.Tx) error { 323 324 dynamicBkt := tx.Bucket(dynamicPluginBucketName) 325 if dynamicBkt == nil { 326 return nil // no previous plugins upgrade 327 } 328 329 oldState := &RegistryState12{} 330 if err := dynamicBkt.Get(registryStateKey, oldState); err != nil { 331 if !boltdd.IsErrNotFound(err) { 332 return fmt.Errorf("failed to read dynamic plugin registry state: %v", err) 333 } 334 } 335 336 newState := &dynamicplugins.RegistryState{ 337 Plugins: make(map[string]map[string]*list.List), 338 } 339 340 for ptype, plugins := range oldState.Plugins { 341 newState.Plugins[ptype] = make(map[string]*list.List) 342 for pname, pluginInfo := range plugins { 343 newState.Plugins[ptype][pname] = list.New() 344 entry := list.Element{Value: pluginInfo} 345 newState.Plugins[ptype][pname].PushFront(entry) 346 } 347 } 348 return dynamicBkt.Put(registryStateKey, newState) 349 }