github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/state/upgrade_int_test.go (about)

     1  package state_test
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/hashicorp/nomad/ci"
    14  	"github.com/hashicorp/nomad/client/allocrunner"
    15  	"github.com/hashicorp/nomad/client/allocwatcher"
    16  	clientconfig "github.com/hashicorp/nomad/client/config"
    17  	"github.com/hashicorp/nomad/client/devicemanager"
    18  	dmstate "github.com/hashicorp/nomad/client/devicemanager/state"
    19  	"github.com/hashicorp/nomad/client/pluginmanager/drivermanager"
    20  	regMock "github.com/hashicorp/nomad/client/serviceregistration/mock"
    21  	. "github.com/hashicorp/nomad/client/state"
    22  	"github.com/hashicorp/nomad/client/vaultclient"
    23  	"github.com/hashicorp/nomad/helper/boltdd"
    24  	"github.com/hashicorp/nomad/helper/testlog"
    25  	"github.com/hashicorp/nomad/nomad/structs"
    26  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"go.etcd.io/bbolt"
    30  )
    31  
    32  // TestBoltStateDB_Upgrade_Ok asserts upgading an old state db does not error
    33  // during upgrade and restore.
    34  func TestBoltStateDB_UpgradeOld_Ok(t *testing.T) {
    35  	ci.Parallel(t)
    36  
    37  	dbFromTestFile := func(t *testing.T, dir, fn string) *BoltStateDB {
    38  
    39  		var src io.ReadCloser
    40  		src, err := os.Open(fn)
    41  		require.NoError(t, err)
    42  		defer src.Close()
    43  
    44  		// testdata may be gzip'd; decode on copy
    45  		if strings.HasSuffix(fn, ".gz") {
    46  			src, err = gzip.NewReader(src)
    47  			require.NoError(t, err)
    48  		}
    49  
    50  		dst, err := os.Create(filepath.Join(dir, "state.db"))
    51  		require.NoError(t, err)
    52  
    53  		// Copy test files before testing them for safety
    54  		_, err = io.Copy(dst, src)
    55  		require.NoError(t, err)
    56  
    57  		require.NoError(t, src.Close())
    58  
    59  		dbI, err := NewBoltStateDB(testlog.HCLogger(t), dir)
    60  		require.NoError(t, err)
    61  
    62  		db := dbI.(*BoltStateDB)
    63  		return db
    64  	}
    65  
    66  	pre09files := []string{
    67  		"testdata/state-0.7.1.db.gz",
    68  		"testdata/state-0.8.6-empty.db.gz",
    69  		"testdata/state-0.8.6-no-deploy.db.gz"}
    70  
    71  	for _, fn := range pre09files {
    72  		t.Run(fn, func(t *testing.T) {
    73  
    74  			dir := t.TempDir()
    75  
    76  			db := dbFromTestFile(t, dir, fn)
    77  			defer db.Close()
    78  
    79  			// Simply opening old files should *not* alter them
    80  			require.NoError(t, db.DB().View(func(tx *boltdd.Tx) error {
    81  				b := tx.Bucket([]byte("meta"))
    82  				if b != nil {
    83  					return fmt.Errorf("meta bucket found but should not exist yet!")
    84  				}
    85  				return nil
    86  			}))
    87  
    88  			to09, to12, err := NeedsUpgrade(db.DB().BoltDB())
    89  			require.NoError(t, err)
    90  			require.True(t, to09)
    91  			require.True(t, to12)
    92  
    93  			// Attempt the upgrade
    94  			require.NoError(t, db.Upgrade())
    95  
    96  			to09, to12, err = NeedsUpgrade(db.DB().BoltDB())
    97  			require.NoError(t, err)
    98  			require.False(t, to09)
    99  			require.False(t, to12)
   100  
   101  			// Ensure Allocations can be restored and
   102  			// NewAR/AR.Restore do not error.
   103  			allocs, errs, err := db.GetAllAllocations()
   104  			require.NoError(t, err)
   105  			assert.Len(t, errs, 0)
   106  
   107  			for _, alloc := range allocs {
   108  				checkUpgradedAlloc(t, dir, db, alloc)
   109  			}
   110  
   111  			// Should be nil for all upgrades
   112  			ps, err := db.GetDevicePluginState()
   113  			require.NoError(t, err)
   114  			require.Nil(t, ps)
   115  
   116  			ps = &dmstate.PluginState{
   117  				ReattachConfigs: map[string]*pstructs.ReattachConfig{
   118  					"test": {Pid: 1},
   119  				},
   120  			}
   121  			require.NoError(t, db.PutDevicePluginState(ps))
   122  
   123  			registry, err := db.GetDynamicPluginRegistryState()
   124  			require.Nil(t, registry)
   125  
   126  			require.NoError(t, err)
   127  			require.NoError(t, db.Close())
   128  		})
   129  	}
   130  
   131  	t.Run("testdata/state-1.2.6.db.gz", func(t *testing.T) {
   132  		fn := "testdata/state-1.2.6.db.gz"
   133  		dir := t.TempDir()
   134  
   135  		db := dbFromTestFile(t, dir, fn)
   136  		defer db.Close()
   137  
   138  		// Simply opening old files should *not* alter them
   139  		db.DB().BoltDB().View(func(tx *bbolt.Tx) error {
   140  			b := tx.Bucket([]byte("meta"))
   141  			if b == nil {
   142  				return fmt.Errorf("meta bucket should exist")
   143  			}
   144  			v := b.Get([]byte("version"))
   145  			if len(v) == 0 {
   146  				return fmt.Errorf("meta version is missing")
   147  			}
   148  			if !bytes.Equal(v, []byte{'2'}) {
   149  				return fmt.Errorf("meta version should not have changed")
   150  			}
   151  			return nil
   152  		})
   153  
   154  		to09, to12, err := NeedsUpgrade(db.DB().BoltDB())
   155  		require.NoError(t, err)
   156  		require.False(t, to09)
   157  		require.True(t, to12)
   158  
   159  		// Attempt the upgrade
   160  		require.NoError(t, db.Upgrade())
   161  
   162  		to09, to12, err = NeedsUpgrade(db.DB().BoltDB())
   163  		require.NoError(t, err)
   164  		require.False(t, to09)
   165  		require.False(t, to12)
   166  
   167  		registry, err := db.GetDynamicPluginRegistryState()
   168  		require.NoError(t, err)
   169  		require.NotNil(t, registry)
   170  		require.Len(t, registry.Plugins["csi-node"], 2)
   171  
   172  		require.NoError(t, db.Close())
   173  	})
   174  }
   175  
   176  // checkUpgradedAlloc creates and restores an AllocRunner from an upgraded
   177  // database.
   178  //
   179  // It does not call AR.Run as its intended to be used against a wide test
   180  // corpus in testdata that may be expensive to run and require unavailable
   181  // dependencies.
   182  func checkUpgradedAlloc(t *testing.T, path string, db StateDB, alloc *structs.Allocation) {
   183  	_, err := db.GetDeploymentStatus(alloc.ID)
   184  	assert.NoError(t, err)
   185  
   186  	tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
   187  	for _, task := range tg.Tasks {
   188  		_, _, err := db.GetTaskRunnerState(alloc.ID, task.Name)
   189  		require.NoError(t, err)
   190  	}
   191  
   192  	clientConf, cleanup := clientconfig.TestClientConfig(t)
   193  
   194  	// Does *not* cleanup overridden StateDir below. That's left alone for
   195  	// the caller to cleanup.
   196  	defer cleanup()
   197  
   198  	clientConf.StateDir = path
   199  
   200  	conf := &allocrunner.Config{
   201  		Alloc:             alloc,
   202  		Logger:            clientConf.Logger,
   203  		ClientConfig:      clientConf,
   204  		StateDB:           db,
   205  		Consul:            regMock.NewServiceRegistrationHandler(clientConf.Logger),
   206  		Vault:             vaultclient.NewMockVaultClient(),
   207  		StateUpdater:      &allocrunner.MockStateUpdater{},
   208  		PrevAllocWatcher:  allocwatcher.NoopPrevAlloc{},
   209  		PrevAllocMigrator: allocwatcher.NoopPrevAlloc{},
   210  		DeviceManager:     devicemanager.NoopMockManager(),
   211  		DriverManager:     drivermanager.TestDriverManager(t),
   212  	}
   213  	ar, err := allocrunner.NewAllocRunner(conf)
   214  	require.NoError(t, err)
   215  
   216  	// AllocRunner.Restore should not error
   217  	require.NoError(t, ar.Restore())
   218  }