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