From cc7875bd3638224fae91a1103ec27bedaa42dc33 Mon Sep 17 00:00:00 2001 From: Wei Fu Date: Sat, 28 Dec 2024 22:12:28 +0000 Subject: [PATCH] *: support custom content check offline in v2store Part of #18993 Signed-off-by: Wei Fu --- etcdutl/ctl.go | 1 + etcdutl/etcdutl/check_command.go | 112 ++++++++++++++++++++++++++ tests/e2e/v2store_deprecation_test.go | 19 +++++ 3 files changed, 132 insertions(+) create mode 100644 etcdutl/etcdutl/check_command.go diff --git a/etcdutl/ctl.go b/etcdutl/ctl.go index a044547c63c6..dbdc5cc7810d 100644 --- a/etcdutl/ctl.go +++ b/etcdutl/ctl.go @@ -41,6 +41,7 @@ func init() { etcdutl.NewDefragCommand(), etcdutl.NewSnapshotCommand(), etcdutl.NewVersionCommand(), + etcdutl.NewCheckCommand(), ) } diff --git a/etcdutl/etcdutl/check_command.go b/etcdutl/etcdutl/check_command.go new file mode 100644 index 000000000000..e7f6992e136e --- /dev/null +++ b/etcdutl/etcdutl/check_command.go @@ -0,0 +1,112 @@ +// Copyright 2024 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package etcdutl + +import ( + "errors" + "fmt" + "path/filepath" + + "github.com/spf13/cobra" + + "go.etcd.io/etcd/pkg/v3/cobrautl" + "go.etcd.io/etcd/server/v3/etcdserver/api/membership" + "go.etcd.io/etcd/server/v3/etcdserver/api/snap" + "go.etcd.io/etcd/server/v3/etcdserver/api/v2store" + "go.etcd.io/etcd/server/v3/wal" +) + +// NewCheckCommand returns the cobra command for "check". +func NewCheckCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "check ", + Short: "commands for checking properties", + } + cmd.AddCommand(NewCheckV2StoreCommand()) + return cmd +} + +var ( + argCheckV2StoreDataDir string +) + +// NewCheckV2StoreCommand returns the cobra command for "check v2store". +func NewCheckV2StoreCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "v2store", + Short: "Check custom content in v2store", + Run: checkV2StoreRunFunc, + } + cmd.Flags().StringVar(&argCheckV2StoreDataDir, "data-dir", "", "Required. A data directory not in use by etcd.") + cmd.MarkFlagRequired("data-dir") + return cmd +} + +func checkV2StoreRunFunc(_ *cobra.Command, _ []string) { + err := checkV2StoreDataDir(argCheckV2StoreDataDir) + if err != nil { + cobrautl.ExitWithError(cobrautl.ExitError, err) + } +} + +func checkV2StoreDataDir(dataDir string) error { + var ( + storeClusterPrefix = "/0" + storeKeysPrefix = "/1" + + lg = GetLogger() + + walDir = filepath.Join(dataDir, "member", "wal") + snapDir = filepath.Join(dataDir, "member", "snap") + ) + + walSnaps, err := wal.ValidSnapshotEntries(lg, walDir) + if err != nil { + if errors.Is(err, wal.ErrFileNotFound) { + return nil + } + return err + } + + ss := snap.New(lg, snapDir) + snapshot, err := ss.LoadNewestAvailable(walSnaps) + if err != nil { + if errors.Is(err, snap.ErrNoSnapshot) { + return nil + } + return err + } + if snapshot == nil { + return nil + } + + st := v2store.New(storeClusterPrefix, storeKeysPrefix) + + if err := st.Recovery(snapshot.Data); err != nil { + return fmt.Errorf("failed to recover v2store from snapshot: %w", err) + } + return assertNoV2StoreContent(st) +} + +func assertNoV2StoreContent(st v2store.Store) error { + metaOnly, err := membership.IsMetaStoreOnly(st) + if err != nil { + return err + } + if metaOnly { + return nil + } + return fmt.Errorf("detected custom content in v2store") +} diff --git a/tests/e2e/v2store_deprecation_test.go b/tests/e2e/v2store_deprecation_test.go index 52dec549b075..08d66c148faf 100644 --- a/tests/e2e/v2store_deprecation_test.go +++ b/tests/e2e/v2store_deprecation_test.go @@ -97,3 +97,22 @@ func TestV2DeprecationWriteOnlyNoV2Api(t *testing.T) { _, err = proc.Expect("--enable-v2 and --v2-deprecation=write-only are mutually exclusive") assert.NoError(t, err) } + +func TestV2DeprecationCheckCustomContentOffline(t *testing.T) { + e2e.BeforeTest(t) + dataDirPath := t.TempDir() + + createV2store(t, dataDirPath) + + assertVerifyCheckCustomContentOffline(t, dataDirPath) +} + +func assertVerifyCheckCustomContentOffline(t *testing.T, dataDirPath string) { + t.Logf("Checking custom content in v2store - %s", dataDirPath) + + proc, err := e2e.SpawnCmd([]string{e2e.BinDir + "/etcdutl", "check", "v2store", "--data-dir=" + dataDirPath}, nil) + assert.NoError(t, err) + + _, err = proc.Expect("detected custom content in v2store") + assert.NoError(t, err) +}