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
15package e2e
16
17import (
18	"fmt"
19	"strings"
20	"testing"
21	"time"
22)
23
24func TestCtlV3Put(t *testing.T)              { testCtl(t, putTest, withDialTimeout(7*time.Second)) }
25func TestCtlV3PutNoTLS(t *testing.T)         { testCtl(t, putTest, withCfg(configNoTLS)) }
26func TestCtlV3PutClientTLS(t *testing.T)     { testCtl(t, putTest, withCfg(configClientTLS)) }
27func TestCtlV3PutClientAutoTLS(t *testing.T) { testCtl(t, putTest, withCfg(configClientAutoTLS)) }
28func TestCtlV3PutPeerTLS(t *testing.T)       { testCtl(t, putTest, withCfg(configPeerTLS)) }
29func TestCtlV3PutTimeout(t *testing.T)       { testCtl(t, putTest, withDialTimeout(0)) }
30func TestCtlV3PutClientTLSFlagByEnv(t *testing.T) {
31	testCtl(t, putTest, withCfg(configClientTLS), withFlagByEnv())
32}
33func TestCtlV3PutIgnoreValue(t *testing.T) { testCtl(t, putTestIgnoreValue) }
34func TestCtlV3PutIgnoreLease(t *testing.T) { testCtl(t, putTestIgnoreLease) }
35
36func TestCtlV3Get(t *testing.T)              { testCtl(t, getTest) }
37func TestCtlV3GetNoTLS(t *testing.T)         { testCtl(t, getTest, withCfg(configNoTLS)) }
38func TestCtlV3GetClientTLS(t *testing.T)     { testCtl(t, getTest, withCfg(configClientTLS)) }
39func TestCtlV3GetClientAutoTLS(t *testing.T) { testCtl(t, getTest, withCfg(configClientAutoTLS)) }
40func TestCtlV3GetPeerTLS(t *testing.T)       { testCtl(t, getTest, withCfg(configPeerTLS)) }
41func TestCtlV3GetTimeout(t *testing.T)       { testCtl(t, getTest, withDialTimeout(0)) }
42func TestCtlV3GetQuorum(t *testing.T)        { testCtl(t, getTest, withQuorum()) }
43
44func TestCtlV3GetFormat(t *testing.T)    { testCtl(t, getFormatTest) }
45func TestCtlV3GetRev(t *testing.T)       { testCtl(t, getRevTest) }
46func TestCtlV3GetKeysOnly(t *testing.T)  { testCtl(t, getKeysOnlyTest) }
47func TestCtlV3GetCountOnly(t *testing.T) { testCtl(t, getCountOnlyTest) }
48
49func TestCtlV3Del(t *testing.T)          { testCtl(t, delTest) }
50func TestCtlV3DelNoTLS(t *testing.T)     { testCtl(t, delTest, withCfg(configNoTLS)) }
51func TestCtlV3DelClientTLS(t *testing.T) { testCtl(t, delTest, withCfg(configClientTLS)) }
52func TestCtlV3DelPeerTLS(t *testing.T)   { testCtl(t, delTest, withCfg(configPeerTLS)) }
53func TestCtlV3DelTimeout(t *testing.T)   { testCtl(t, delTest, withDialTimeout(0)) }
54
55func TestCtlV3GetRevokedCRL(t *testing.T) {
56	cfg := etcdProcessClusterConfig{
57		clusterSize:           1,
58		initialToken:          "new",
59		clientTLS:             clientTLS,
60		isClientCRL:           true,
61		clientCertAuthEnabled: true,
62	}
63	testCtl(t, testGetRevokedCRL, withCfg(cfg))
64}
65
66func testGetRevokedCRL(cx ctlCtx) {
67	// test reject
68	if err := ctlV3Put(cx, "k", "v", ""); err == nil || !strings.Contains(err.Error(), "Error:") {
69		cx.t.Fatalf("expected reset connection on put, got %v", err)
70	}
71	// test accept
72	cx.epc.cfg.isClientCRL = false
73	if err := ctlV3Put(cx, "k", "v", ""); err != nil {
74		cx.t.Fatal(err)
75	}
76}
77
78func putTest(cx ctlCtx) {
79	key, value := "foo", "bar"
80
81	if err := ctlV3Put(cx, key, value, ""); err != nil {
82		if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
83			cx.t.Fatalf("putTest ctlV3Put error (%v)", err)
84		}
85	}
86	if err := ctlV3Get(cx, []string{key}, kv{key, value}); err != nil {
87		if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
88			cx.t.Fatalf("putTest ctlV3Get error (%v)", err)
89		}
90	}
91}
92
93func putTestIgnoreValue(cx ctlCtx) {
94	if err := ctlV3Put(cx, "foo", "bar", ""); err != nil {
95		cx.t.Fatal(err)
96	}
97	if err := ctlV3Get(cx, []string{"foo"}, kv{"foo", "bar"}); err != nil {
98		cx.t.Fatal(err)
99	}
100	if err := ctlV3Put(cx, "foo", "", "", "--ignore-value"); err != nil {
101		cx.t.Fatal(err)
102	}
103	if err := ctlV3Get(cx, []string{"foo"}, kv{"foo", "bar"}); err != nil {
104		cx.t.Fatal(err)
105	}
106}
107
108func putTestIgnoreLease(cx ctlCtx) {
109	leaseID, err := ctlV3LeaseGrant(cx, 10)
110	if err != nil {
111		cx.t.Fatalf("putTestIgnoreLease: ctlV3LeaseGrant error (%v)", err)
112	}
113	if err := ctlV3Put(cx, "foo", "bar", leaseID); err != nil {
114		cx.t.Fatalf("putTestIgnoreLease: ctlV3Put error (%v)", err)
115	}
116	if err := ctlV3Get(cx, []string{"foo"}, kv{"foo", "bar"}); err != nil {
117		cx.t.Fatalf("putTestIgnoreLease: ctlV3Get error (%v)", err)
118	}
119	if err := ctlV3Put(cx, "foo", "bar1", "", "--ignore-lease"); err != nil {
120		cx.t.Fatalf("putTestIgnoreLease: ctlV3Put error (%v)", err)
121	}
122	if err := ctlV3Get(cx, []string{"foo"}, kv{"foo", "bar1"}); err != nil {
123		cx.t.Fatalf("putTestIgnoreLease: ctlV3Get error (%v)", err)
124	}
125	if err := ctlV3LeaseRevoke(cx, leaseID); err != nil {
126		cx.t.Fatalf("putTestIgnoreLease: ctlV3LeaseRevok error (%v)", err)
127	}
128	if err := ctlV3Get(cx, []string{"key"}); err != nil { // expect no output
129		cx.t.Fatalf("putTestIgnoreLease: ctlV3Get error (%v)", err)
130	}
131}
132
133func getTest(cx ctlCtx) {
134	var (
135		kvs    = []kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}}
136		revkvs = []kv{{"key3", "val3"}, {"key2", "val2"}, {"key1", "val1"}}
137	)
138	for i := range kvs {
139		if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil {
140			cx.t.Fatalf("getTest #%d: ctlV3Put error (%v)", i, err)
141		}
142	}
143
144	tests := []struct {
145		args []string
146
147		wkv []kv
148	}{
149		{[]string{"key1"}, []kv{{"key1", "val1"}}},
150		{[]string{"", "--prefix"}, kvs},
151		{[]string{"", "--from-key"}, kvs},
152		{[]string{"key", "--prefix"}, kvs},
153		{[]string{"key", "--prefix", "--limit=2"}, kvs[:2]},
154		{[]string{"key", "--prefix", "--order=ASCEND", "--sort-by=MODIFY"}, kvs},
155		{[]string{"key", "--prefix", "--order=ASCEND", "--sort-by=VERSION"}, kvs},
156		{[]string{"key", "--prefix", "--sort-by=CREATE"}, kvs}, // ASCEND by default
157		{[]string{"key", "--prefix", "--order=DESCEND", "--sort-by=CREATE"}, revkvs},
158		{[]string{"key", "--prefix", "--order=DESCEND", "--sort-by=KEY"}, revkvs},
159	}
160	for i, tt := range tests {
161		if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil {
162			if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
163				cx.t.Errorf("getTest #%d: ctlV3Get error (%v)", i, err)
164			}
165		}
166	}
167}
168
169func getFormatTest(cx ctlCtx) {
170	if err := ctlV3Put(cx, "abc", "123", ""); err != nil {
171		cx.t.Fatal(err)
172	}
173
174	tests := []struct {
175		format    string
176		valueOnly bool
177
178		wstr string
179	}{
180		{"simple", false, "abc"},
181		{"simple", true, "123"},
182		{"json", false, `"kvs":[{"key":"YWJj"`},
183		{"protobuf", false, "\x17\b\x93\xe7\xf6\x93\xd4ņ\xe14\x10\xed"},
184	}
185
186	for i, tt := range tests {
187		cmdArgs := append(cx.PrefixArgs(), "get")
188		cmdArgs = append(cmdArgs, "--write-out="+tt.format)
189		if tt.valueOnly {
190			cmdArgs = append(cmdArgs, "--print-value-only")
191		}
192		cmdArgs = append(cmdArgs, "abc")
193		if err := spawnWithExpect(cmdArgs, tt.wstr); err != nil {
194			cx.t.Errorf("#%d: error (%v), wanted %v", i, err, tt.wstr)
195		}
196	}
197}
198
199func getRevTest(cx ctlCtx) {
200	var (
201		kvs = []kv{{"key", "val1"}, {"key", "val2"}, {"key", "val3"}}
202	)
203	for i := range kvs {
204		if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil {
205			cx.t.Fatalf("getRevTest #%d: ctlV3Put error (%v)", i, err)
206		}
207	}
208
209	tests := []struct {
210		args []string
211
212		wkv []kv
213	}{
214		{[]string{"key", "--rev", "2"}, kvs[:1]},
215		{[]string{"key", "--rev", "3"}, kvs[1:2]},
216		{[]string{"key", "--rev", "4"}, kvs[2:]},
217	}
218
219	for i, tt := range tests {
220		if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil {
221			cx.t.Errorf("getTest #%d: ctlV3Get error (%v)", i, err)
222		}
223	}
224}
225
226func getKeysOnlyTest(cx ctlCtx) {
227	if err := ctlV3Put(cx, "key", "val", ""); err != nil {
228		cx.t.Fatal(err)
229	}
230	cmdArgs := append(cx.PrefixArgs(), []string{"get", "--keys-only", "key"}...)
231	if err := spawnWithExpect(cmdArgs, "key"); err != nil {
232		cx.t.Fatal(err)
233	}
234	if err := spawnWithExpects(cmdArgs, "val"); err == nil {
235		cx.t.Fatalf("got value but passed --keys-only")
236	}
237}
238
239func getCountOnlyTest(cx ctlCtx) {
240	cmdArgs := append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
241	if err := spawnWithExpects(cmdArgs, "\"Count\" : 0"); err != nil {
242		cx.t.Fatal(err)
243	}
244	if err := ctlV3Put(cx, "key", "val", ""); err != nil {
245		cx.t.Fatal(err)
246	}
247	cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
248	if err := spawnWithExpects(cmdArgs, "\"Count\" : 1"); err != nil {
249		cx.t.Fatal(err)
250	}
251	if err := ctlV3Put(cx, "key1", "val", ""); err != nil {
252		cx.t.Fatal(err)
253	}
254	if err := ctlV3Put(cx, "key1", "val", ""); err != nil {
255		cx.t.Fatal(err)
256	}
257	cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
258	if err := spawnWithExpects(cmdArgs, "\"Count\" : 2"); err != nil {
259		cx.t.Fatal(err)
260	}
261	if err := ctlV3Put(cx, "key2", "val", ""); err != nil {
262		cx.t.Fatal(err)
263	}
264	cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key", "--prefix", "--write-out=fields"}...)
265	if err := spawnWithExpects(cmdArgs, "\"Count\" : 3"); err != nil {
266		cx.t.Fatal(err)
267	}
268	expected := []string{
269		"\"Count\" : 3",
270	}
271	cmdArgs = append(cx.PrefixArgs(), []string{"get", "--count-only", "key3", "--prefix", "--write-out=fields"}...)
272	if err := spawnWithExpects(cmdArgs, expected...); err == nil {
273		cx.t.Fatal(err)
274	}
275}
276
277func delTest(cx ctlCtx) {
278	tests := []struct {
279		puts []kv
280		args []string
281
282		deletedNum int
283	}{
284		{ // delete all keys
285			[]kv{{"foo1", "bar"}, {"foo2", "bar"}, {"foo3", "bar"}},
286			[]string{"", "--prefix"},
287			3,
288		},
289		{ // delete all keys
290			[]kv{{"foo1", "bar"}, {"foo2", "bar"}, {"foo3", "bar"}},
291			[]string{"", "--from-key"},
292			3,
293		},
294		{
295			[]kv{{"this", "value"}},
296			[]string{"that"},
297			0,
298		},
299		{
300			[]kv{{"sample", "value"}},
301			[]string{"sample"},
302			1,
303		},
304		{
305			[]kv{{"key1", "val1"}, {"key2", "val2"}, {"key3", "val3"}},
306			[]string{"key", "--prefix"},
307			3,
308		},
309		{
310			[]kv{{"zoo1", "bar"}, {"zoo2", "bar2"}, {"zoo3", "bar3"}},
311			[]string{"zoo1", "--from-key"},
312			3,
313		},
314	}
315
316	for i, tt := range tests {
317		for j := range tt.puts {
318			if err := ctlV3Put(cx, tt.puts[j].key, tt.puts[j].val, ""); err != nil {
319				cx.t.Fatalf("delTest #%d-%d: ctlV3Put error (%v)", i, j, err)
320			}
321		}
322		if err := ctlV3Del(cx, tt.args, tt.deletedNum); err != nil {
323			if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
324				cx.t.Fatalf("delTest #%d: ctlV3Del error (%v)", i, err)
325			}
326		}
327	}
328}
329
330func ctlV3Put(cx ctlCtx, key, value, leaseID string, flags ...string) error {
331	skipValue := false
332	skipLease := false
333	for _, f := range flags {
334		if f == "--ignore-value" {
335			skipValue = true
336		}
337		if f == "--ignore-lease" {
338			skipLease = true
339		}
340	}
341	cmdArgs := append(cx.PrefixArgs(), "put", key)
342	if !skipValue {
343		cmdArgs = append(cmdArgs, value)
344	}
345	if leaseID != "" && !skipLease {
346		cmdArgs = append(cmdArgs, "--lease", leaseID)
347	}
348	if len(flags) != 0 {
349		cmdArgs = append(cmdArgs, flags...)
350	}
351	return spawnWithExpect(cmdArgs, "OK")
352}
353
354type kv struct {
355	key, val string
356}
357
358func ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error {
359	cmdArgs := append(cx.PrefixArgs(), "get")
360	cmdArgs = append(cmdArgs, args...)
361	if !cx.quorum {
362		cmdArgs = append(cmdArgs, "--consistency", "s")
363	}
364	var lines []string
365	for _, elem := range kvs {
366		lines = append(lines, elem.key, elem.val)
367	}
368	return spawnWithExpects(cmdArgs, lines...)
369}
370
371// ctlV3GetWithErr runs "get" command expecting no output but error
372func ctlV3GetWithErr(cx ctlCtx, args []string, errs []string) error {
373	cmdArgs := append(cx.PrefixArgs(), "get")
374	cmdArgs = append(cmdArgs, args...)
375	if !cx.quorum {
376		cmdArgs = append(cmdArgs, "--consistency", "s")
377	}
378	return spawnWithExpects(cmdArgs, errs...)
379}
380
381func ctlV3Del(cx ctlCtx, args []string, num int) error {
382	cmdArgs := append(cx.PrefixArgs(), "del")
383	cmdArgs = append(cmdArgs, args...)
384	return spawnWithExpects(cmdArgs, fmt.Sprintf("%d", num))
385}
386