Skip to content

Commit d5e8f96

Browse files
committed
storage, fs: move pebble cluster version compatibility check to InitEnv
Previously, we performed the CRDB version compatibility check for pebble and the currently running cluster in the pebble constructor. This commit moves that check to the file system environment initialization so that we can colocate with fs encyrption initialization. We also extend the version checking to validate that pebble's minversion is not too high for the current cockroach version, to guard against backwards incompatible changes. Fixes: #148213 Epic: none Release note: None
1 parent 3832f42 commit d5e8f96

File tree

4 files changed

+111
-55
lines changed

4 files changed

+111
-55
lines changed

pkg/cli/auto_decrypt_fs_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,13 @@ func TestAutoDecryptFS(t *testing.T) {
7272
expected := `
7373
$TMPDIR/path1: mkdir-all: 0777
7474
$TMPDIR/path1: lock: LOCK
75+
$TMPDIR/path1: open: STORAGE_MIN_VERSION
7576
$TMPDIR/path1: mkdir-all: $TMPDIR/path1 0755
7677
$TMPDIR/path1: create: $TMPDIR/path1/bar
7778
$TMPDIR/path1: close: $TMPDIR/path1/bar
7879
$TMPDIR/foo/path2: mkdir-all: 0777
7980
$TMPDIR/foo/path2: lock: LOCK
81+
$TMPDIR/foo/path2: open: STORAGE_MIN_VERSION
8082
$TMPDIR/foo/path2: mkdir-all: $TMPDIR/foo/path2 0755
8183
$TMPDIR/foo/path2: create: $TMPDIR/foo/path2/baz
8284
$TMPDIR/foo/path2: close: $TMPDIR/foo/path2/baz

pkg/storage/fs/fs.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,54 @@ func InitEnv(
204204
return nil, err
205205
}
206206

207+
// Read the current store cluster version.
208+
storeClusterVersion, minVerFileExists, err := minversion.GetMinVersion(e.UnencryptedFS, e.Dir)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
if minVerFileExists {
214+
v := cfg.Version
215+
if v == nil {
216+
return nil, errors.New("version must not be nil when min-version file exists")
217+
}
218+
// Avoid running a binary too new for this store. This is what you'd catch
219+
// if, say, you restarted directly from v21.2 into v22.2 (bumping the min
220+
// version) without going through v22.1 first.
221+
//
222+
// Note that "going through" above means that v22.1 successfully upgrades
223+
// all existing stores. If v22.1 crashes half-way through the startup
224+
// sequence (so now some stores have v21.2, but others v22.1) you are
225+
// expected to run v22.1 again (hopefully without the crash this time) which
226+
// would then rewrite all the stores.
227+
if storeClusterVersion.Less(v.MinSupportedVersion()) {
228+
if storeClusterVersion.Major < clusterversion.DevOffset && v.LatestVersion().Major >= clusterversion.DevOffset {
229+
return nil, errors.Errorf(
230+
"store last used with cockroach non-development version v%s "+
231+
"cannot be opened by development version v%s",
232+
storeClusterVersion, v.LatestVersion(),
233+
)
234+
}
235+
return nil, errors.Errorf(
236+
"store last used with cockroach version v%s "+
237+
"is too old for running version v%s (which requires data from v%s or later)",
238+
storeClusterVersion, v.LatestVersion(), v.MinSupportedVersion(),
239+
)
240+
}
241+
242+
// Avoid running a binary too old for this store. This protects against
243+
// scenarios where an older binary attempts to open a store created by
244+
// a newer version that may have incompatible data structures or formats.
245+
if v.LatestVersion().Less(storeClusterVersion) {
246+
return nil, errors.Errorf(
247+
"store last used with cockroach version v%s is too high for running "+
248+
"version v%s",
249+
storeClusterVersion, v.LatestVersion(),
250+
)
251+
}
252+
e.StoreClusterVersion = storeClusterVersion
253+
}
254+
207255
// Validate and configure encryption-at-rest. If no encryption-at-rest
208256
// configuration was provided, resolveEncryptedEnvOptions will validate that
209257
// there is no file registry.
@@ -241,6 +289,11 @@ type Env struct {
241289
// the store. It provides access to encryption-at-rest stats, etc.
242290
Encryption *EncryptionEnv
243291

292+
// StoreClusterVersion is the version of the store as read from the
293+
// min-version file. This value will be empty if the min-version file
294+
// does not exist (eg, the store is being created for the first time).
295+
StoreClusterVersion roachpb.Version
296+
244297
// defaultFS is the primary VFS that most users should use. If
245298
// encryption-at-rest is enabled, this VFS will handle transparently
246299
// encrypting and decrypting as necessary. When the Env is used as an

pkg/storage/pebble.go

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,54 +1076,25 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
10761076
cfg.opts.Experimental.RemoteStorage = remoteStorageAdaptor{p: p, ctx: logCtx, factory: cfg.remoteStorageFactory}
10771077
}
10781078
}
1079-
1080-
// Read the current store cluster version.
1081-
storeClusterVersion, minVerFileExists, err := minversion.GetMinVersion(p.cfg.env.UnencryptedFS, cfg.env.Dir)
1082-
if err != nil {
1083-
return nil, err
1084-
}
1085-
if minVerFileExists {
1086-
// Avoid running a binary too new for this store. This is what you'd catch
1087-
// if, say, you restarted directly from v21.2 into v22.2 (bumping the min
1088-
// version) without going through v22.1 first.
1089-
//
1090-
// Note that "going through" above means that v22.1 successfully upgrades
1091-
// all existing stores. If v22.1 crashes half-way through the startup
1092-
// sequence (so now some stores have v21.2, but others v22.1) you are
1093-
// expected to run v22.1 again (hopefully without the crash this time) which
1094-
// would then rewrite all the stores.
1095-
if v := cfg.settings.Version; storeClusterVersion.Less(v.MinSupportedVersion()) {
1096-
if storeClusterVersion.Major < clusterversion.DevOffset && v.LatestVersion().Major >= clusterversion.DevOffset {
1097-
return nil, errors.Errorf(
1098-
"store last used with cockroach non-development version v%s "+
1099-
"cannot be opened by development version v%s",
1100-
storeClusterVersion, v.LatestVersion(),
1101-
)
1102-
}
1103-
return nil, errors.Errorf(
1104-
"store last used with cockroach version v%s "+
1105-
"is too old for running version v%s (which requires data from v%s or later)",
1106-
storeClusterVersion, v.LatestVersion(), v.MinSupportedVersion(),
1107-
)
1108-
}
1109-
cfg.opts.ErrorIfNotExists = true
1110-
} else {
1111-
if cfg.opts.ErrorIfNotExists || cfg.opts.ReadOnly {
1112-
// Make sure the message is not confusing if the store does exist but
1113-
// there is no min version file.
1114-
filename := p.cfg.env.UnencryptedFS.PathJoin(cfg.env.Dir, minversion.MinVersionFilename)
1115-
return nil, errors.Errorf(
1116-
"pebble: database %q does not exist (missing required file %q)",
1117-
cfg.env.Dir, filename,
1118-
)
1119-
}
1120-
// If there is no min version file, there should be no store. If there is
1121-
// one, it's either 1) a store from a very old version (which we don't want
1122-
// to open) or 2) an empty store that was created from a previous bootstrap
1123-
// attempt that failed right before writing out the min version file. We set
1124-
// a flag to disallow the open in case 1.
1125-
cfg.opts.ErrorIfNotPristine = true
1079+
// If the store cluster version is not empty, it means that we initialized
1080+
// the env using a min-version file. We can use that to determine if the
1081+
// store should already exist or not.
1082+
initFromMinVersionFile := cfg.env.StoreClusterVersion != (roachpb.Version{})
1083+
1084+
if !initFromMinVersionFile && (cfg.opts.ErrorIfNotExists || cfg.opts.ReadOnly) {
1085+
filename := p.cfg.env.UnencryptedFS.PathJoin(cfg.env.Dir, minversion.MinVersionFilename)
1086+
return nil, errors.Errorf(
1087+
"pebble: database %q does not exist (missing required file %q)",
1088+
cfg.env.Dir, filename,
1089+
)
11261090
}
1091+
cfg.opts.ErrorIfNotExists = cfg.opts.ErrorIfNotExists || initFromMinVersionFile
1092+
// If there is no min version file, there should be no store. If there is
1093+
// one, it's either 1) a store from a very old version (which we don't want
1094+
// to open) or 2) an empty store that was created from a previous bootstrap
1095+
// attempt that failed right before writing out the min version file. We set
1096+
// a flag to disallow the open in case 1.
1097+
cfg.opts.ErrorIfNotPristine = !initFromMinVersionFile
11271098

11281099
if WorkloadCollectorEnabled {
11291100
p.replayer.Attach(cfg.opts)
@@ -1132,10 +1103,10 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
11321103
db, err := pebble.Open(cfg.env.Dir, cfg.opts)
11331104
if err != nil {
11341105
// Decorate the errors caused by the flags we set above.
1135-
if minVerFileExists && errors.Is(err, pebble.ErrDBDoesNotExist) {
1106+
if initFromMinVersionFile && errors.Is(err, pebble.ErrDBDoesNotExist) {
11361107
err = errors.Wrap(err, "min version file exists but store doesn't")
11371108
}
1138-
if !minVerFileExists && errors.Is(err, pebble.ErrDBNotPristine) {
1109+
if !initFromMinVersionFile && errors.Is(err, pebble.ErrDBNotPristine) {
11391110
err = errors.Wrap(err, "store has no min-version file; this can "+
11401111
"happen if the store was created by an old CockroachDB version that is no "+
11411112
"longer supported")
@@ -1144,7 +1115,8 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
11441115
}
11451116
p.db = db
11461117

1147-
if !minVerFileExists {
1118+
storeClusterVersion := cfg.env.StoreClusterVersion
1119+
if storeClusterVersion == (roachpb.Version{}) {
11481120
storeClusterVersion = cfg.settings.Version.ActiveVersionOrEmpty(ctx).Version
11491121
if storeClusterVersion == (roachpb.Version{}) {
11501122
// If there is no active version, use the minimum supported version.

pkg/storage/pebble_test.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,16 +1308,45 @@ func TestIncompatibleVersion(t *testing.T) {
13081308
require.NoError(t, err)
13091309
require.NoError(t, fs.SafeWriteToUnencryptedFile(memFS, "", minversion.MinVersionFilename, b, fs.UnspecifiedWriteCategory))
13101310

1311-
env = mustInitTestEnv(t, memFS, "")
1312-
_, err = Open(ctx, env, cluster.MakeTestingClusterSettings())
1313-
require.Error(t, err)
1314-
_, err = Open(ctx, env, cluster.MakeTestingClusterSettings())
1311+
settings := cluster.MakeTestingClusterSettings()
1312+
_, err = fs.InitEnv(context.Background(), memFS, "", fs.EnvConfig{
1313+
Version: settings.Version,
1314+
}, nil /* statsCollector */)
13151315
msg := err.Error()
13161316
if !strings.Contains(msg, "is too old for running version") &&
13171317
!strings.Contains(msg, "cannot be opened by development version") {
13181318
t.Fatalf("unexpected error %v", err)
13191319
}
1320-
env.Close()
1320+
}
1321+
1322+
func TestPebbleClusterVersionTooNew(t *testing.T) {
1323+
defer leaktest.AfterTest(t)()
1324+
defer log.Scope(t).Close(t)
1325+
ctx := context.Background()
1326+
1327+
memFS := vfs.NewMem()
1328+
env := mustInitTestEnv(t, memFS, "")
1329+
1330+
p, err := Open(ctx, env, cluster.MakeTestingClusterSettings())
1331+
require.NoError(t, err)
1332+
p.Close()
1333+
1334+
// Overwrite the min version file with a future version that's newer than the
1335+
// latest supported version. Use a development version higher than the current
1336+
// running version to ensure it will be considered "too new". We use a very
1337+
// high development version to avoid conflicts with the current version.
1338+
ver := roachpb.Version{Major: 1000030, Minor: 0}
1339+
b, err := protoutil.Marshal(&ver)
1340+
require.NoError(t, err)
1341+
require.NoError(t, fs.SafeWriteToUnencryptedFile(memFS, "", minversion.MinVersionFilename, b, fs.UnspecifiedWriteCategory))
1342+
1343+
settings := cluster.MakeTestingClusterSettings()
1344+
_, err = fs.InitEnv(context.Background(), memFS, "", fs.EnvConfig{
1345+
Version: settings.Version,
1346+
}, nil /* statsCollector */)
1347+
require.Error(t, err)
1348+
msg := err.Error()
1349+
require.Contains(t, msg, "is too high for running version")
13211350
}
13221351

13231352
func TestNoMinVerFile(t *testing.T) {

0 commit comments

Comments
 (0)