Skip to content

Commit c2d3f7b

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 8d099d4 commit c2d3f7b

File tree

10 files changed

+132
-81
lines changed

10 files changed

+132
-81
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ go_library(
281281
"//pkg/sql/sem/tree",
282282
"//pkg/sql/ttl/ttlbase",
283283
"//pkg/storage/enginepb",
284-
"//pkg/storage/minversion",
284+
"//pkg/storage/fs",
285285
"//pkg/testutils",
286286
"//pkg/testutils/fingerprintutils",
287287
"//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/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ go_library(
77
"encryption_at_rest.go",
88
"file_registry.go",
99
"fs.go",
10+
"min_version.go",
1011
"sticky_vfs.go",
1112
"temp_dir.go",
1213
"test_utils.go",
@@ -22,7 +23,6 @@ go_library(
2223
"//pkg/settings/cluster",
2324
"//pkg/storage/disk",
2425
"//pkg/storage/enginepb",
25-
"//pkg/storage/minversion",
2626
"//pkg/storage/storageconfig",
2727
"//pkg/util/buildutil",
2828
"//pkg/util/envutil",

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/fs/min_version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Use of this software is governed by the CockroachDB Software License
44
// included in the /LICENSE file.
55

6-
package minversion
6+
package fs
77

88
import (
99
"io"

pkg/storage/min_version.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ package storage
88
import (
99
"github.com/cockroachdb/cockroach/pkg/roachpb"
1010
"github.com/cockroachdb/cockroach/pkg/storage/fs"
11-
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
12-
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
11+
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
1312
"github.com/cockroachdb/errors"
1413
"github.com/cockroachdb/pebble/vfs"
1514
)
@@ -22,7 +21,7 @@ func writeMinVersionFile(atomicRenameFS vfs.FS, dir string, version roachpb.Vers
2221
if version == (roachpb.Version{}) {
2322
return errors.New("min version should not be empty")
2423
}
25-
ok, err := minversion.MinVersionIsAtLeastTargetVersion(atomicRenameFS, dir, version)
24+
ok, err := fs.MinVersionIsAtLeastTargetVersion(atomicRenameFS, dir, version)
2625
if err != nil {
2726
return err
2827
}
@@ -33,7 +32,7 @@ func writeMinVersionFile(atomicRenameFS vfs.FS, dir string, version roachpb.Vers
3332
if err != nil {
3433
return err
3534
}
36-
filename := atomicRenameFS.PathJoin(dir, minversion.MinVersionFilename)
35+
filename := atomicRenameFS.PathJoin(dir, fs.MinVersionFilename)
3736
if err := fs.SafeWriteToUnencryptedFile(atomicRenameFS, dir, filename, b, fs.UnspecifiedWriteCategory); err != nil {
3837
return err
3938
}

pkg/storage/min_version_test.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/cockroachdb/cockroach/pkg/roachpb"
1515
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
1616
"github.com/cockroachdb/cockroach/pkg/storage/fs"
17-
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
1817
"github.com/cockroachdb/cockroach/pkg/storage/storageconfig"
1918
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
2019
"github.com/cockroachdb/cockroach/pkg/util/log"
@@ -34,56 +33,56 @@ func TestMinVersion(t *testing.T) {
3433
require.NoError(t, mem.MkdirAll(dir, os.ModeDir))
3534

3635
// Expect !ok min version file doesn't exist.
37-
v, ok, err := minversion.GetMinVersion(mem, dir)
36+
v, ok, err := fs.GetMinVersion(mem, dir)
3837
require.NoError(t, err)
3938
require.Equal(t, roachpb.Version{}, v)
4039
require.False(t, ok)
4140

4241
// Expect min version to not be at least any target version.
43-
ok, err = minversion.MinVersionIsAtLeastTargetVersion(mem, dir, version1)
42+
ok, err = fs.MinVersionIsAtLeastTargetVersion(mem, dir, version1)
4443
require.NoError(t, err)
4544
require.False(t, ok)
46-
ok, err = minversion.MinVersionIsAtLeastTargetVersion(mem, dir, version2)
45+
ok, err = fs.MinVersionIsAtLeastTargetVersion(mem, dir, version2)
4746
require.NoError(t, err)
4847
require.False(t, ok)
4948

5049
// Expect no error when updating min version if no file currently exists.
5150
require.NoError(t, writeMinVersionFile(mem, dir, version1))
5251

5352
// Expect min version to be version1.
54-
v, ok, err = minversion.GetMinVersion(mem, dir)
53+
v, ok, err = fs.GetMinVersion(mem, dir)
5554
require.NoError(t, err)
5655
require.True(t, ok)
5756
require.True(t, version1.Equal(v))
5857

5958
// Expect min version to be at least version1 but not version2.
60-
ok, err = minversion.MinVersionIsAtLeastTargetVersion(mem, dir, version1)
59+
ok, err = fs.MinVersionIsAtLeastTargetVersion(mem, dir, version1)
6160
require.NoError(t, err)
6261
require.True(t, ok)
63-
ok, err = minversion.MinVersionIsAtLeastTargetVersion(mem, dir, version2)
62+
ok, err = fs.MinVersionIsAtLeastTargetVersion(mem, dir, version2)
6463
require.NoError(t, err)
6564
require.False(t, ok)
6665

6766
// Expect no error when updating min version to a higher version.
6867
require.NoError(t, writeMinVersionFile(mem, dir, version2))
6968

7069
// Expect min version to be at least version1 and version2.
71-
ok, err = minversion.MinVersionIsAtLeastTargetVersion(mem, dir, version1)
70+
ok, err = fs.MinVersionIsAtLeastTargetVersion(mem, dir, version1)
7271
require.NoError(t, err)
7372
require.True(t, ok)
74-
ok, err = minversion.MinVersionIsAtLeastTargetVersion(mem, dir, version2)
73+
ok, err = fs.MinVersionIsAtLeastTargetVersion(mem, dir, version2)
7574
require.NoError(t, err)
7675
require.True(t, ok)
7776

7877
// Expect min version to be version2.
79-
v, ok, err = minversion.GetMinVersion(mem, dir)
78+
v, ok, err = fs.GetMinVersion(mem, dir)
8079
require.NoError(t, err)
8180
require.True(t, ok)
8281
require.True(t, version2.Equal(v))
8382

8483
// Expect no-op when trying to update min version to a lower version.
8584
require.NoError(t, writeMinVersionFile(mem, dir, version1))
86-
v, ok, err = minversion.GetMinVersion(mem, dir)
85+
v, ok, err = fs.GetMinVersion(mem, dir)
8786
require.NoError(t, err)
8887
require.True(t, ok)
8988
require.True(t, version2.Equal(v))
@@ -138,7 +137,7 @@ func TestMinVersion_IsNotEncrypted(t *testing.T) {
138137

139138
// Reading the file directly through the unencrypted MemFS should
140139
// succeed and yield the correct version.
141-
v, ok, err := minversion.GetMinVersion(env.UnencryptedFS, "")
140+
v, ok, err := fs.GetMinVersion(env.UnencryptedFS, "")
142141
require.NoError(t, err)
143142
require.True(t, ok)
144143
require.Equal(t, st.Version.LatestVersion(), v)

pkg/storage/pebble.go

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
"github.com/cockroachdb/cockroach/pkg/storage/disk"
3333
"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
3434
"github.com/cockroachdb/cockroach/pkg/storage/fs"
35-
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
3635
"github.com/cockroachdb/cockroach/pkg/storage/mvccencoding"
3736
"github.com/cockroachdb/cockroach/pkg/storage/pebbleiter"
3837
"github.com/cockroachdb/cockroach/pkg/util/buildutil"
@@ -1076,54 +1075,25 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
10761075
cfg.opts.Experimental.RemoteStorage = remoteStorageAdaptor{p: p, ctx: logCtx, factory: cfg.remoteStorageFactory}
10771076
}
10781077
}
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{})
10791082

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
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+
)
11261089
}
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
11271097

11281098
if WorkloadCollectorEnabled {
11291099
p.replayer.Attach(cfg.opts)
@@ -1132,10 +1102,10 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
11321102
db, err := pebble.Open(cfg.env.Dir, cfg.opts)
11331103
if err != nil {
11341104
// Decorate the errors caused by the flags we set above.
1135-
if minVerFileExists && errors.Is(err, pebble.ErrDBDoesNotExist) {
1105+
if initFromMinVersionFile && errors.Is(err, pebble.ErrDBDoesNotExist) {
11361106
err = errors.Wrap(err, "min version file exists but store doesn't")
11371107
}
1138-
if !minVerFileExists && errors.Is(err, pebble.ErrDBNotPristine) {
1108+
if !initFromMinVersionFile && errors.Is(err, pebble.ErrDBNotPristine) {
11391109
err = errors.Wrap(err, "store has no min-version file; this can "+
11401110
"happen if the store was created by an old CockroachDB version that is no "+
11411111
"longer supported")
@@ -1144,7 +1114,8 @@ func newPebble(ctx context.Context, cfg engineConfig) (p *Pebble, err error) {
11441114
}
11451115
p.db = db
11461116

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

0 commit comments

Comments
 (0)