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  }