Skip to content

Commit dd829df

Browse files
committed
Add mirror test implementation
- Implement mirror test structure - Add test/common functionality - Create ctl_v3_make_mirror_test Signed-off-by: Ronald Ngounou <[email protected]> Signed-off-by: ronaldngounou <[email protected]>
1 parent 1b2ed5a commit dd829df

File tree

10 files changed

+289
-5
lines changed

10 files changed

+289
-5
lines changed

tests/common/e2e_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,37 @@ func WithUnixClient() config.ClusterOption {
120120
}
121121

122122
func WithTCPClient() config.ClusterOption {
123-
return e2e.WithTCPClient()
123+
return func(c *config.ClusterConfig) {
124+
ctx := ensureE2EClusterContext(c)
125+
ctx.UseUnix = false
126+
c.ClusterContext = ctx
127+
}
128+
}
129+
130+
func WithBasePort(port int) config.ClusterOption {
131+
return func(c *config.ClusterConfig) {
132+
ctx := ensureE2EClusterContext(c)
133+
ctx.BasePort = port
134+
c.ClusterContext = ctx
135+
}
136+
}
137+
138+
func ensureE2EClusterContext(c *config.ClusterConfig) *e2e.ClusterContext {
139+
ctx, _ := c.ClusterContext.(*e2e.ClusterContext)
140+
if ctx == nil {
141+
ctx = &e2e.ClusterContext{}
142+
}
143+
return ctx
144+
}
145+
146+
func configureMirrorDestTLS(mm *config.MakeMirrorOptions, tls config.TLSConfig) {
147+
switch tls {
148+
case config.ManualTLS:
149+
mm.DestCert = e2e.CertPath
150+
mm.DestKey = e2e.PrivateKeyPath
151+
mm.DestCACert = e2e.CaPath
152+
mm.DestInsecureTransport = false
153+
default:
154+
mm.DestInsecureTransport = true
155+
}
124156
}

tests/common/integration_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,23 @@ func WithUnixClient() config.ClusterOption {
6565
}
6666

6767
func WithTCPClient() config.ClusterOption {
68-
return integration.WithTCPClient()
68+
return func(c *config.ClusterConfig) {
69+
ctx := ensureIntegrationClusterContext(c)
70+
ctx.UseUnix = false
71+
c.ClusterContext = ctx
72+
}
73+
}
74+
75+
func WithBasePort(port int) config.ClusterOption {
76+
return func(c *config.ClusterConfig) {}
6977
}
78+
79+
func ensureIntegrationClusterContext(c *config.ClusterConfig) *integration.ClusterContext {
80+
ctx, _ := c.ClusterContext.(*integration.ClusterContext)
81+
if ctx == nil {
82+
ctx = &integration.ClusterContext{}
83+
}
84+
return ctx
85+
}
86+
87+
func configureMirrorDestTLS(mm *config.MakeMirrorOptions, _ config.TLSConfig) {}

tests/common/make_mirror_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2016 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 common
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"testing"
21+
"time"
22+
23+
"github.com/stretchr/testify/require"
24+
25+
"go.etcd.io/etcd/tests/v3/framework/config"
26+
"go.etcd.io/etcd/tests/v3/framework/testutils"
27+
)
28+
29+
func TestMakeMirrorModifyDestPrefix(t *testing.T) {
30+
testRunner.BeforeTest(t)
31+
for _, srcTC := range clusterTestCases() {
32+
for _, destTC := range clusterTestCases() {
33+
t.Run(fmt.Sprintf("Source=%s/Destination=%s", srcTC.name, destTC.name), func(t *testing.T) {
34+
var (
35+
mmOpts = config.MakeMirrorOptions{Prefix: "o_", DestPrefix: "d_"}
36+
sourcekvs = []testutils.KV{{Key: "o_key1", Val: "val1"}, {Key: "o_key2", Val: "val2"}, {Key: "o_key3", Val: "val3"}}
37+
destkvs = []testutils.KV{{Key: "d_key1", Val: "val1"}, {Key: "d_key2", Val: "val2"}, {Key: "d_key3", Val: "val3"}}
38+
srcprefix = "o_"
39+
destprefix = "d_"
40+
)
41+
42+
testMirror(t, srcTC, destTC, mmOpts, sourcekvs, destkvs, srcprefix, destprefix)
43+
})
44+
}
45+
}
46+
}
47+
48+
func TestMakeMirrorNoDestPrefix(t *testing.T) {
49+
testRunner.BeforeTest(t)
50+
for _, srcTC := range clusterTestCases() {
51+
for _, destTC := range clusterTestCases() {
52+
t.Run(fmt.Sprintf("Source=%s/Destination=%s", srcTC.name, destTC.name), func(t *testing.T) {
53+
var (
54+
mmOpts = config.MakeMirrorOptions{Prefix: "o_", NoDestPrefix: true}
55+
sourcekvs = []testutils.KV{{Key: "o_key1", Val: "val1"}, {Key: "o_key2", Val: "val2"}, {Key: "o_key3", Val: "val3"}}
56+
destkvs = []testutils.KV{{Key: "key1", Val: "val1"}, {Key: "key2", Val: "val2"}, {Key: "key3", Val: "val3"}}
57+
srcprefix = "o_"
58+
destprefix = "key"
59+
)
60+
61+
testMirror(t, srcTC, destTC, mmOpts, sourcekvs, destkvs, srcprefix, destprefix)
62+
})
63+
}
64+
}
65+
}
66+
67+
func TestMakeMirror(t *testing.T) {
68+
testRunner.BeforeTest(t)
69+
for _, srcTC := range clusterTestCases() {
70+
for _, destTC := range clusterTestCases() {
71+
t.Run(fmt.Sprintf("Source=%s/Destination=%s", srcTC.name, destTC.name), func(t *testing.T) {
72+
var (
73+
mmOpts config.MakeMirrorOptions
74+
sourcekvs = []testutils.KV{{Key: "key1", Val: "val1"}, {Key: "key2", Val: "val2"}, {Key: "key3", Val: "val3"}}
75+
destkvs = []testutils.KV{{Key: "key1", Val: "val1"}, {Key: "key2", Val: "val2"}, {Key: "key3", Val: "val3"}}
76+
prefix = "key"
77+
)
78+
79+
testMirror(t, srcTC, destTC, mmOpts, sourcekvs, destkvs, prefix, prefix)
80+
})
81+
}
82+
}
83+
}
84+
85+
func TestMakeMirrorWithWatchRev(t *testing.T) {
86+
testRunner.BeforeTest(t)
87+
for _, srcTC := range clusterTestCases() {
88+
for _, destTC := range clusterTestCases() {
89+
t.Run(fmt.Sprintf("Source=%s/Destination=%s", srcTC.name, destTC.name), func(t *testing.T) {
90+
var (
91+
mmOpts = config.MakeMirrorOptions{Prefix: "o_", NoDestPrefix: true, Rev: 4}
92+
sourcekvs = []testutils.KV{{Key: "o_key1", Val: "val1"}, {Key: "o_key2", Val: "val2"}, {Key: "o_key3", Val: "val3"}, {Key: "o_key4", Val: "val4"}}
93+
destkvs = []testutils.KV{{Key: "key3", Val: "val3"}, {Key: "key4", Val: "val4"}}
94+
srcprefix = "o_"
95+
destprefix = "key"
96+
)
97+
98+
testMirror(t, srcTC, destTC, mmOpts, sourcekvs, destkvs, srcprefix, destprefix)
99+
})
100+
}
101+
}
102+
}
103+
104+
func testMirror(t *testing.T, srcTC, destTC testCase, mmOpts config.MakeMirrorOptions, sourcekvs, destkvs []testutils.KV, srcprefix, destprefix string) {
105+
t.Helper()
106+
107+
if destTC.config.ClientTLS == config.AutoTLS {
108+
// AutoTLS destination unsupported here: we cannot pass the destination CA to etcdctl (--dest-cacert)
109+
// so tLS verification would fail.
110+
t.Skip("Skipping: destingation uses Client AutoTLS, but the test cannot expose the dest CA for etcdctl (--dest-cacert); tLS verification would fail.")
111+
}
112+
113+
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
114+
defer cancel()
115+
// Source cluster
116+
src := testRunner.NewCluster(ctx, t, config.WithClusterConfig(srcTC.config))
117+
defer src.Close()
118+
srcClient := testutils.MustClient(src.Client())
119+
120+
// Destination cluster
121+
destCfg := destTC.config
122+
dest := testRunner.NewCluster(ctx, t, config.WithClusterConfig(destTC.config), WithBasePort(10000)) // destTC.BasePort
123+
defer dest.Close()
124+
destClient := testutils.MustClient(dest.Client())
125+
126+
// Configure TLS for destination before starting make-mirror
127+
configureMirrorDestTLS(&mmOpts, destCfg.ClientTLS)
128+
129+
// Start make mirror
130+
errCh := make(chan error)
131+
go func(opts config.MakeMirrorOptions) {
132+
errCh <- srcClient.MakeMirror(ctx, dest.Endpoints()[0], opts)
133+
}(mmOpts)
134+
defer func() {
135+
// Need to cancel the context to ensure the MakeMirror goroutine is cancelled before catching the error.
136+
cancel()
137+
require.NoError(t, <-errCh)
138+
}()
139+
140+
// Write to source
141+
for i := range sourcekvs {
142+
require.NoError(t, srcClient.Put(ctx, sourcekvs[i].Key, sourcekvs[i].Val, config.PutOptions{}))
143+
}
144+
145+
// Source assertion
146+
srcResp, err := srcClient.Get(ctx, srcprefix, config.GetOptions{Prefix: true})
147+
require.NoError(t, err)
148+
require.Equal(t, sourcekvs, testutils.KeyValuesFromGetResponse(srcResp))
149+
150+
// Destination assertion
151+
wCtx, wCancel := context.WithCancel(ctx)
152+
defer wCancel()
153+
154+
watchChan := destClient.Watch(wCtx, destprefix, config.WatchOptions{Prefix: true, Revision: 1})
155+
156+
// Compare the result of what we obtained using the make-mirror command versus what we had using
157+
// the Watch command
158+
destResp, err := testutils.KeyValuesFromWatchChan(watchChan, len(destkvs), 10*time.Second)
159+
require.NoError(t, err)
160+
require.Equal(t, destkvs, destResp)
161+
}

tests/common/unit_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,9 @@ func WithTCPClient() config.ClusterOption {
5656
func WithUnixClient() config.ClusterOption {
5757
return func(c *config.ClusterConfig) {}
5858
}
59+
60+
func WithBasePort(port int) config.ClusterOption {
61+
return func(c *config.ClusterConfig) {}
62+
}
63+
64+
func configureMirrorDestTLS(mm *config.MakeMirrorOptions, _ config.TLSConfig) {}

tests/framework/config/client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,14 @@ type WatchOptions struct {
7777
Revision int64
7878
RangeEnd string
7979
}
80+
81+
type MakeMirrorOptions struct {
82+
Prefix string
83+
Rev int64
84+
DestPrefix string
85+
NoDestPrefix bool
86+
DestCACert string
87+
DestCert string
88+
DestKey string
89+
DestInsecureTransport bool
90+
}

tests/framework/e2e/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ func (cv ClusterVersion) String() string {
4141
}
4242

4343
type ClusterContext struct {
44-
Version ClusterVersion
45-
EnvVars map[string]string
46-
UseUnix bool
44+
Version ClusterVersion
45+
EnvVars map[string]string
46+
UseUnix bool
47+
BasePort int
4748
}
4849

4950
func WithHTTP2Debug() config.ClusterOption {

tests/framework/e2e/e2e.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ func (e e2eRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config
6666
if ctx.UseUnix {
6767
e2eConfig.BaseClientScheme = "unix"
6868
}
69+
if ctx.BasePort != 0 {
70+
e2eConfig.BasePort = ctx.BasePort
71+
}
6972
}
7073

7174
switch cfg.ClientTLS {

tests/framework/e2e/etcdctl.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,3 +745,48 @@ func (ctl *EtcdctlV3) Watch(ctx context.Context, key string, opts config.WatchOp
745745

746746
return ch
747747
}
748+
749+
func (ctl *EtcdctlV3) MakeMirror(ctx context.Context, destEndpoints string, opts config.MakeMirrorOptions) error {
750+
args := ctl.cmdArgs()
751+
args = append(args, "make-mirror")
752+
753+
if opts.Prefix != "" {
754+
args = append(args, "--prefix", opts.Prefix)
755+
}
756+
if opts.Rev != 0 {
757+
args = append(args, "--rev", fmt.Sprint(opts.Rev))
758+
}
759+
if opts.NoDestPrefix {
760+
args = append(args, "--no-dest-prefix")
761+
}
762+
if opts.DestPrefix != "" {
763+
args = append(args, "--dest-prefix", opts.DestPrefix)
764+
}
765+
if opts.DestCACert != "" {
766+
args = append(args, "--dest-cacert", opts.DestCACert)
767+
}
768+
if opts.DestCert != "" {
769+
args = append(args, "--dest-cert", opts.DestCert)
770+
}
771+
if opts.DestKey != "" {
772+
args = append(args, "--dest-key", opts.DestKey)
773+
}
774+
if opts.DestInsecureTransport {
775+
// Bool flags in cobra must be provided as --flag=false, not as separate args.
776+
args = append(args, "--dest-insecure-transport", "--dest-insecure-transport=false")
777+
}
778+
779+
args = append(args, destEndpoints)
780+
781+
proc, err := SpawnCmd(args, nil)
782+
if err != nil {
783+
return err
784+
}
785+
786+
defer proc.Stop()
787+
788+
// Wait until context is cancelled or its timeout is reached so that the make-mirror command can keep running in the background.
789+
<-ctx.Done()
790+
791+
return nil
792+
}

tests/framework/integration/integration.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package integration
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021
"strings"
2122
"testing"
@@ -475,3 +476,7 @@ func (c integrationClient) MemberList(ctx context.Context, serializable bool) (*
475476
}
476477
return c.Client.MemberList(ctx)
477478
}
479+
480+
func (c integrationClient) MakeMirror(ctx context.Context, destEndpoints string, opts config.MakeMirrorOptions) error {
481+
return errors.New("errors")
482+
}

tests/framework/interfaces/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ type Client interface {
8484
MemberRemove(ctx context.Context, id uint64) (*clientv3.MemberRemoveResponse, error)
8585

8686
Watch(ctx context.Context, key string, opts config.WatchOptions) clientv3.WatchChan
87+
88+
MakeMirror(ctx context.Context, destEndpoints string, opts config.MakeMirrorOptions) error
8789
}
8890

8991
type TemplateEndpoints interface {

0 commit comments

Comments
 (0)