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  }