Skip to content

Commit bba2c2a

Browse files
authored
Merge pull request etcd-io#19113 from fuweid/v35_offline_check_v2store
[3.5] support custom content check offline in v2store
2 parents 9d920a8 + 5d284fd commit bba2c2a

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

etcdutl/ctl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func init() {
4141
etcdutl.NewDefragCommand(),
4242
etcdutl.NewSnapshotCommand(),
4343
etcdutl.NewVersionCommand(),
44+
etcdutl.NewCheckCommand(),
4445
)
4546
}
4647

etcdutl/etcdutl/check_command.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2024 The etcd Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package etcdutl
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"path/filepath"
21+
22+
"github.com/spf13/cobra"
23+
24+
"go.etcd.io/etcd/pkg/v3/cobrautl"
25+
"go.etcd.io/etcd/server/v3/etcdserver"
26+
"go.etcd.io/etcd/server/v3/etcdserver/api/membership"
27+
"go.etcd.io/etcd/server/v3/etcdserver/api/snap"
28+
"go.etcd.io/etcd/server/v3/etcdserver/api/v2store"
29+
"go.etcd.io/etcd/server/v3/wal"
30+
)
31+
32+
// NewCheckCommand returns the cobra command for "check".
33+
func NewCheckCommand() *cobra.Command {
34+
cmd := &cobra.Command{
35+
Use: "check <subcommand>",
36+
Short: "commands for checking properties",
37+
}
38+
cmd.AddCommand(NewCheckV2StoreCommand())
39+
return cmd
40+
}
41+
42+
var (
43+
argCheckV2StoreDataDir string
44+
)
45+
46+
// NewCheckV2StoreCommand returns the cobra command for "check v2store".
47+
func NewCheckV2StoreCommand() *cobra.Command {
48+
cmd := &cobra.Command{
49+
Use: "v2store",
50+
Short: "Check custom content in v2store",
51+
Run: checkV2StoreRunFunc,
52+
}
53+
cmd.Flags().StringVar(&argCheckV2StoreDataDir, "data-dir", "", "Required. A data directory not in use by etcd.")
54+
cmd.MarkFlagRequired("data-dir")
55+
return cmd
56+
}
57+
58+
func checkV2StoreRunFunc(_ *cobra.Command, _ []string) {
59+
err := checkV2StoreDataDir(argCheckV2StoreDataDir)
60+
if err != nil {
61+
cobrautl.ExitWithError(cobrautl.ExitError, err)
62+
}
63+
fmt.Println("No custom content found in v2store.")
64+
}
65+
66+
func checkV2StoreDataDir(dataDir string) error {
67+
var (
68+
lg = GetLogger()
69+
70+
walDir = filepath.Join(dataDir, "member", "wal")
71+
snapDir = filepath.Join(dataDir, "member", "snap")
72+
)
73+
74+
walSnaps, err := wal.ValidSnapshotEntries(lg, walDir)
75+
if err != nil {
76+
if errors.Is(err, wal.ErrFileNotFound) {
77+
return nil
78+
}
79+
return err
80+
}
81+
82+
ss := snap.New(lg, snapDir)
83+
snapshot, err := ss.LoadNewestAvailable(walSnaps)
84+
if err != nil {
85+
if errors.Is(err, snap.ErrNoSnapshot) {
86+
return nil
87+
}
88+
return err
89+
}
90+
if snapshot == nil {
91+
return nil
92+
}
93+
94+
st := v2store.New(etcdserver.StoreClusterPrefix, etcdserver.StoreKeysPrefix)
95+
96+
if err := st.Recovery(snapshot.Data); err != nil {
97+
return fmt.Errorf("failed to recover v2store from snapshot: %w", err)
98+
}
99+
return assertNoV2StoreContent(st)
100+
}
101+
102+
func assertNoV2StoreContent(st v2store.Store) error {
103+
metaOnly, err := membership.IsMetaStoreOnly(st)
104+
if err != nil {
105+
return err
106+
}
107+
if metaOnly {
108+
return nil
109+
}
110+
return fmt.Errorf("detected custom content in v2store")
111+
}

tests/e2e/v2store_deprecation_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,60 @@ func TestV2DeprecationWriteOnlyNoV2Api(t *testing.T) {
9797
_, err = proc.Expect("--enable-v2 and --v2-deprecation=write-only are mutually exclusive")
9898
assert.NoError(t, err)
9999
}
100+
101+
func TestV2DeprecationCheckCustomContentOffline(t *testing.T) {
102+
e2e.BeforeTest(t)
103+
104+
t.Run("WithCustomContent", func(t *testing.T) {
105+
dataDirPath := t.TempDir()
106+
107+
createV2store(t, dataDirPath)
108+
109+
assertVerifyCheckCustomContentOffline(t, dataDirPath)
110+
})
111+
112+
t.Run("WithoutCustomContent", func(t *testing.T) {
113+
dataDirPath := ""
114+
115+
func() {
116+
cCtx := getDefaultCtlCtx(t)
117+
118+
cfg := cCtx.cfg
119+
cfg.ClusterSize = 3
120+
cfg.SnapshotCount = 5
121+
cfg.EnableV2 = true
122+
123+
// create a cluster with 3 members
124+
epc, err := e2e.NewEtcdProcessCluster(t, &cfg)
125+
assert.NoError(t, err)
126+
127+
cCtx.epc = epc
128+
dataDirPath = epc.Procs[0].Config().DataDirPath
129+
130+
defer func() {
131+
assert.NoError(t, epc.Stop())
132+
}()
133+
134+
// create key-values with v3 api
135+
for i := 0; i < 10; i++ {
136+
assert.NoError(t, ctlV3Put(cCtx, fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i), ""))
137+
}
138+
}()
139+
140+
proc, err := e2e.SpawnCmd([]string{e2e.BinDir + "/etcdutl", "check", "v2store", "--data-dir=" + dataDirPath}, nil)
141+
assert.NoError(t, err)
142+
143+
_, err = proc.Expect("No custom content found in v2store")
144+
assert.NoError(t, err)
145+
})
146+
}
147+
148+
func assertVerifyCheckCustomContentOffline(t *testing.T, dataDirPath string) {
149+
t.Logf("Checking custom content in v2store - %s", dataDirPath)
150+
151+
proc, err := e2e.SpawnCmd([]string{e2e.BinDir + "/etcdutl", "check", "v2store", "--data-dir=" + dataDirPath}, nil)
152+
assert.NoError(t, err)
153+
154+
_, err = proc.Expect("detected custom content in v2store")
155+
assert.NoError(t, err)
156+
}

0 commit comments

Comments
 (0)