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  }