Skip to content

Commit 662d064

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 e064707 commit 662d064

File tree

11 files changed

+170
-81
lines changed

11 files changed

+170
-81
lines changed

pkg/cmd/roachtest/tests/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ go_library(
280280
"//pkg/sql/pgwire/pgerror",
281281
"//pkg/sql/sem/tree",
282282
"//pkg/sql/ttl/ttlbase",
283-
"//pkg/storage",
284283
"//pkg/storage/enginepb",
284+
"//pkg/storage/minversion",
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"
26+
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
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", storage.MinVersionFilename), "{store-dir}/"+name)
284+
c.Run(ctx, option.WithNodes(c.All()), "cp", fmt.Sprintf("{store-dir}/%s", minversion.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/kv/kvserver/logstore/sideload_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,6 @@ func TestSideloadStorageSync(t *testing.T) {
598598
// Reset filesystem to the last synced state.
599599

600600
// Emulate process restart. Load from the last synced state.
601-
settings := cluster.MakeTestingClusterSettings()
602601
env, err = fs.InitEnv(ctx, crashFS, "", fs.EnvConfig{
603602
Version: settings.Version,
604603
}, nil /* statsCollector */)

pkg/storage/engine_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,10 @@ func TestCreateCheckpoint(t *testing.T) {
10721072
}
10731073

10741074
func mustInitTestEnv(t testing.TB, baseFS vfs.FS, dir string) *fs.Env {
1075-
e, err := fs.InitEnv(context.Background(), baseFS, dir, fs.EnvConfig{}, nil /* statsCollector */)
1075+
settings := cluster.MakeTestingClusterSettings()
1076+
e, err := fs.InitEnv(context.Background(), baseFS, dir, fs.EnvConfig{
1077+
Version: settings.Version,
1078+
}, nil /* statsCollector */)
10761079
require.NoError(t, err)
10771080
return e
10781081
}

pkg/storage/fs/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ go_library(
1717
"//pkg/base",
1818
"//pkg/cli/exit",
1919
"//pkg/clusterversion",
20+
"//pkg/roachpb",
2021
"//pkg/settings",
22+
"//pkg/settings/cluster",
2123
"//pkg/storage/disk",
2224
"//pkg/storage/enginepb",
25+
"//pkg/storage/minversion",
2326
"//pkg/storage/storageconfig",
2427
"//pkg/util/buildutil",
2528
"//pkg/util/envutil",

pkg/storage/fs/fs.go

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ import (
1616
"github.com/cockroachdb/cockroach/pkg/base"
1717
"github.com/cockroachdb/cockroach/pkg/cli/exit"
1818
"github.com/cockroachdb/cockroach/pkg/clusterversion"
19+
"github.com/cockroachdb/cockroach/pkg/roachpb"
1920
"github.com/cockroachdb/cockroach/pkg/settings"
21+
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
2022
"github.com/cockroachdb/cockroach/pkg/storage/disk"
23+
"github.com/cockroachdb/cockroach/pkg/storage/minversion"
2124
"github.com/cockroachdb/cockroach/pkg/storage/storageconfig"
2225
"github.com/cockroachdb/cockroach/pkg/util/buildutil"
2326
"github.com/cockroachdb/cockroach/pkg/util/envutil"
@@ -201,6 +204,54 @@ func InitEnv(
201204
return nil, err
202205
}
203206

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")
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+
204255
// Validate and configure encryption-at-rest. If no encryption-at-rest
205256
// configuration was provided, resolveEncryptedEnvOptions will validate that
206257
// there is no file registry.
@@ -238,6 +289,11 @@ type Env struct {
238289
// the store. It provides access to encryption-at-rest stats, etc.
239290
Encryption *EncryptionEnv
240291

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+
241297
// defaultFS is the primary VFS that most users should use. If
242298
// encryption-at-rest is enabled, this VFS will handle transparently
243299
// encrypting and decrypting as necessary. When the Env is used as an
@@ -317,7 +373,12 @@ func (e *Env) onDiskSlow(info vfs.DiskSlowInfo) {
317373

318374
// InMemory constructs a new in-memory environment.
319375
func InMemory() *Env {
320-
e, err := InitEnv(context.Background(), vfs.NewMem(), "" /* dir */, EnvConfig{}, nil /* diskWriteStats */)
376+
// For in-memory environments, we create a dummy cluster settings to provide
377+
// a version handle, since these environments don't persist version information.
378+
settings := cluster.MakeTestingClusterSettings()
379+
e, err := InitEnv(context.Background(), vfs.NewMem(), "" /* dir */, EnvConfig{
380+
Version: settings.Version,
381+
}, nil /* diskWriteStats */)
321382
// In practice InitEnv is infallible with this configuration.
322383
if err != nil {
323384
panic(err)
@@ -330,7 +391,10 @@ func InMemory() *Env {
330391
// encryption-at-rest. Since this function ignores the possibility of
331392
// encryption-at-rest, it should only be used as a testing convenience.
332393
func MustInitPhysicalTestingEnv(dir string) *Env {
333-
e, err := InitEnv(context.Background(), vfs.Default, dir, EnvConfig{}, nil /* diskWriteStats */)
394+
settings := cluster.MakeTestingClusterSettings()
395+
e, err := InitEnv(context.Background(), vfs.Default, dir, EnvConfig{
396+
Version: settings.Version,
397+
}, nil /* diskWriteStats */)
334398
if err != nil {
335399
panic(err)
336400
}

pkg/storage/metamorphic/generator.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ type engineConfig struct {
3636
}
3737

3838
func (e *engineConfig) create(path string, baseFS vfs.FS) (storage.Engine, error) {
39-
env, err := fs.InitEnv(context.Background(), baseFS, path, fs.EnvConfig{}, nil /* diskWriteStats */)
39+
settings := cluster.MakeTestingClusterSettings()
40+
env, err := fs.InitEnv(context.Background(), baseFS, path, fs.EnvConfig{
41+
Version: settings.Version,
42+
}, nil /* diskWriteStats */)
4043
if err != nil {
4144
return nil, err
4245
}

pkg/storage/min_version_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,13 @@ func TestSetMinVersion(t *testing.T) {
9393
defer leaktest.AfterTest(t)()
9494
defer log.Scope(t).Close(t)
9595

96-
p, err := Open(context.Background(), InMemory(), cluster.MakeClusterSettings(), CacheSize(0))
96+
settings := cluster.MakeClusterSettings()
97+
e, err := fs.InitEnv(context.Background(), vfs.NewMem(), "" /* dir */, fs.EnvConfig{
98+
Version: settings.Version,
99+
}, nil /* diskWriteStats */)
100+
// In practice InitEnv is infallible with this configuration.
101+
require.NoError(t, err)
102+
p, err := Open(context.Background(), e, settings, CacheSize(0))
97103
require.NoError(t, err)
98104
defer p.Close()
99105
require.Equal(t, MinimumSupportedFormatVersion, p.db.FormatMajorVersion())
@@ -121,7 +127,7 @@ func TestMinVersion_IsNotEncrypted(t *testing.T) {
121127
baseFS := vfs.NewMem()
122128
env, err := fs.InitEnv(ctx, baseFS, "", fs.EnvConfig{
123129
EncryptionOptions: &storageconfig.EncryptionOptions{},
124-
Version: st.Version,
130+
Version: st.Version,
125131
}, nil /* statsCollector */)
126132
require.NoError(t, err)
127133

pkg/storage/open_test.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,27 @@ func TestWALFailover(t *testing.T) {
5050
return nil
5151
}
5252

53+
var settings *cluster.Settings
5354
datadriven.RunTest(t, datapathutils.TestDataPath(t, "wal_failover_config"),
5455
func(t *testing.T, td *datadriven.TestData) string {
5556
switch td.Cmd {
5657
case "mkenv":
58+
// Mock a cluster version, defaulting to latest.
59+
version := clusterversion.Latest.Version()
60+
if td.HasArg("min-version") {
61+
var major, minor int
62+
td.ScanArgs(t, "min-version", &major, &minor)
63+
version = roachpb.Version{
64+
Major: int32(major),
65+
Minor: int32(minor),
66+
}
67+
}
68+
// Match the current offsetting policy.
69+
if clusterversion.Latest.Version().Major > clusterversion.DevOffset {
70+
version.Major += clusterversion.DevOffset
71+
}
72+
settings = cluster.MakeTestingClusterSettingsWithVersions(version, version, true /* initializeVersion */)
73+
5774
dir := td.CmdArgs[0].String()
5875
if e := getEnv(dir); e != nil {
5976
return fmt.Sprintf("env %s already exists", e.Dir)
@@ -62,6 +79,7 @@ func TestWALFailover(t *testing.T) {
6279
require.NoError(t, memfs.MkdirAll(dir, os.ModePerm))
6380

6481
var envConfig fs.EnvConfig
82+
envConfig.Version = settings.Version
6583
if td.HasArg("encrypted-at-rest") {
6684
envConfig.EncryptionOptions = &storageconfig.EncryptionOptions{}
6785
}
@@ -107,22 +125,6 @@ func TestWALFailover(t *testing.T) {
107125
}
108126
openEnv.Ref()
109127

110-
// Mock a cluster version, defaulting to latest.
111-
version := clusterversion.Latest.Version()
112-
if td.HasArg("min-version") {
113-
var major, minor int
114-
td.ScanArgs(t, "min-version", &major, &minor)
115-
version = roachpb.Version{
116-
Major: int32(major),
117-
Minor: int32(minor),
118-
}
119-
}
120-
// Match the current offsetting policy.
121-
if clusterversion.Latest.Version().Major > clusterversion.DevOffset {
122-
version.Major += clusterversion.DevOffset
123-
}
124-
settings := cluster.MakeTestingClusterSettingsWithVersions(version, version, true /* initializeVersion */)
125-
126128
engine, err := Open(context.Background(), openEnv, settings, WALFailover(cfg, envs, defaultFS, nil))
127129
if err != nil {
128130
openEnv.Close()

pkg/storage/pebble.go

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

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

1149-
if !minVerFileExists {
1120+
storeClusterVersion := cfg.env.StoreClusterVersion
1121+
if storeClusterVersion == (roachpb.Version{}) {
11501122
storeClusterVersion = cfg.settings.Version.ActiveVersionOrEmpty(ctx).Version
11511123
if storeClusterVersion == (roachpb.Version{}) {
11521124
// If there is no active version, use the minimum supported version.

0 commit comments

Comments
 (0)