Skip to content

Commit ab81bf1

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 3ab0cf0 commit ab81bf1

File tree

6 files changed

+114
-58
lines changed

6 files changed

+114
-58
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/cmd/roachtest/tests/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ go_library(
281281
"//pkg/sql/sem/tree",
282282
"//pkg/sql/ttl/ttlbase",
283283
"//pkg/storage/enginepb",
284+
"//pkg/storage/fs",
284285
"//pkg/testutils",
285286
"//pkg/testutils/fingerprintutils",
286287
"//pkg/testutils/floatcmp",

pkg/cmd/roachtest/tests/versionupgrade.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"github.com/cockroachdb/cockroach/pkg/cmd/roachtest/test"
2424
"github.com/cockroachdb/cockroach/pkg/roachprod/install"
2525
"github.com/cockroachdb/cockroach/pkg/roachprod/logger"
26-
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
26+
"github.com/cockroachdb/cockroach/pkg/storage/fs"
2727
"github.com/cockroachdb/cockroach/pkg/testutils/release"
2828
"github.com/cockroachdb/cockroach/pkg/ts/tspb"
2929
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
@@ -281,7 +281,7 @@ func makeVersionFixtureAndFatal(
281281
// #54761.
282282
c.Run(ctx, option.WithNodes(c.Node(1)), "cp", "{store-dir}/cluster-bootstrapped", "{store-dir}/"+name)
283283
// Similar to the above - newer versions require the min version file to open a store.
284-
c.Run(ctx, option.WithNodes(c.All()), "cp", fmt.Sprintf("{store-dir}/%s", minversion.MinVersionFilename), "{store-dir}/"+name)
284+
c.Run(ctx, option.WithNodes(c.All()), "cp", fmt.Sprintf("{store-dir}/%s", fs.MinVersionFilename), "{store-dir}/"+name)
285285
c.Run(ctx, option.WithNodes(c.All()), "tar", "-C", "{store-dir}/"+name, "-czf", "{log-dir}/"+name+".tgz", ".")
286286
t.Fatalf(`successfully created checkpoints; failing test on purpose.
287287

pkg/storage/fs/fs.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"github.com/cockroachdb/cockroach/pkg/settings"
2121
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
2222
"github.com/cockroachdb/cockroach/pkg/storage/disk"
23-
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
2423
"github.com/cockroachdb/cockroach/pkg/storage/storageconfig"
2524
"github.com/cockroachdb/cockroach/pkg/util/buildutil"
2625
"github.com/cockroachdb/cockroach/pkg/util/envutil"
@@ -204,6 +203,54 @@ func InitEnv(
204203
return nil, err
205204
}
206205

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

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

11271098
if WorkloadCollectorEnabled {
11281099
p.replayer.Attach(cfg.opts)
@@ -1131,10 +1102,10 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
11311102
db, err := pebble.Open(cfg.env.Dir, cfg.opts)
11321103
if err != nil {
11331104
// Decorate the errors caused by the flags we set above.
1134-
if minVerFileExists && errors.Is(err, pebble.ErrDBDoesNotExist) {
1105+
if initFromMinVersionFile && errors.Is(err, pebble.ErrDBDoesNotExist) {
11351106
err = errors.Wrap(err, "min version file exists but store doesn't")
11361107
}
1137-
if !minVerFileExists && errors.Is(err, pebble.ErrDBNotPristine) {
1108+
if !initFromMinVersionFile && errors.Is(err, pebble.ErrDBNotPristine) {
11381109
err = errors.Wrap(err, "store has no min-version file; this can "+
11391110
"happen if the store was created by an old CockroachDB version that is no "+
11401111
"longer supported")
@@ -1143,7 +1114,8 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
11431114
}
11441115
p.db = db
11451116

1146-
if !minVerFileExists {
1117+
storeClusterVersion := cfg.env.StoreClusterVersion
1118+
if storeClusterVersion == (roachpb.Version{}) {
11471119
storeClusterVersion = cfg.settings.Version.ActiveVersionOrEmpty(ctx).Version
11481120
if storeClusterVersion == (roachpb.Version{}) {
11491121
// 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
@@ -1307,16 +1307,45 @@ func TestIncompatibleVersion(t *testing.T) {
13071307
require.NoError(t, err)
13081308
require.NoError(t, fs.SafeWriteToUnencryptedFile(memFS, "", fs.MinVersionFilename, b, fs.UnspecifiedWriteCategory))
13091309

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

13221351
func TestNoMinVerFile(t *testing.T) {

0 commit comments

Comments
 (0)