diff --git a/tests/common/compact_test.go b/tests/common/compact_test.go index 8effa94e127..d21d206aac3 100644 --- a/tests/common/compact_test.go +++ b/tests/common/compact_test.go @@ -47,7 +47,7 @@ func TestCompact(t *testing.T) { testutils.ExecuteWithTimeout(t, 10*time.Second, func() { var kvs = []testutils.KV{{Key: "key", Val: "val1"}, {Key: "key", Val: "val2"}, {Key: "key", Val: "val3"}} for i := range kvs { - if err := clus.Client().Put(kvs[i].Key, kvs[i].Val); err != nil { + if err := clus.Client().Put(kvs[i].Key, kvs[i].Val, config.PutOptions{}); err != nil { t.Fatalf("compactTest #%d: put kv error (%v)", i, err) } } diff --git a/tests/common/defrag_test.go b/tests/common/defrag_test.go index 6cfce4c6807..2c7db2b7cd1 100644 --- a/tests/common/defrag_test.go +++ b/tests/common/defrag_test.go @@ -30,7 +30,7 @@ func TestDefragOnline(t *testing.T) { defer clus.Close() var kvs = []testutils.KV{{Key: "key", Val: "val1"}, {Key: "key", Val: "val2"}, {Key: "key", Val: "val3"}} for i := range kvs { - if err := clus.Client().Put(kvs[i].Key, kvs[i].Val); err != nil { + if err := clus.Client().Put(kvs[i].Key, kvs[i].Val, config.PutOptions{}); err != nil { t.Fatalf("compactTest #%d: put kv error (%v)", i, err) } } diff --git a/tests/common/kv_test.go b/tests/common/kv_test.go index 2a4eccacc91..6f5849ee959 100644 --- a/tests/common/kv_test.go +++ b/tests/common/kv_test.go @@ -60,7 +60,7 @@ func TestKVPut(t *testing.T) { testutils.ExecuteWithTimeout(t, 10*time.Second, func() { key, value := "foo", "bar" - if err := cc.Put(key, value); err != nil { + if err := cc.Put(key, value, config.PutOptions{}); err != nil { t.Fatalf("count not put key %q, err: %s", key, err) } resp, err := cc.Get(key, config.GetOptions{Serializable: true}) @@ -123,7 +123,7 @@ func TestKVGet(t *testing.T) { ) for i := range kvs { - if err := cc.Put(kvs[i], "bar"); err != nil { + if err := cc.Put(kvs[i], "bar", config.PutOptions{}); err != nil { t.Fatalf("count not put key %q, err: %s", kvs[i], err) } } @@ -246,7 +246,7 @@ func TestKVDelete(t *testing.T) { } for _, tt := range tests { for i := range kvs { - if err := cc.Put(kvs[i], "bar"); err != nil { + if err := cc.Put(kvs[i], "bar", config.PutOptions{}); err != nil { t.Fatalf("count not put key %q, err: %s", kvs[i], err) } } diff --git a/tests/common/lease_test.go b/tests/common/lease_test.go new file mode 100644 index 00000000000..e64f3b86e34 --- /dev/null +++ b/tests/common/lease_test.go @@ -0,0 +1,322 @@ +// Copyright 2022 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 common + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/config" + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +func TestLeaseGrantTimeToLive(t *testing.T) { + testRunner.BeforeTest(t) + + tcs := []struct { + name string + config config.ClusterConfig + }{ + { + name: "NoTLS", + config: config.ClusterConfig{ClusterSize: 1}, + }, + { + name: "PeerTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS}, + }, + { + name: "PeerAutoTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS}, + }, + { + name: "ClientTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS}, + }, + { + name: "ClientAutoTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, tc.config) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + ttl := int64(10) + leaseResp, err := cc.Grant(ttl) + require.NoError(t, err) + + ttlResp, err := cc.TimeToLive(leaseResp.ID, config.LeaseOption{}) + require.NoError(t, err) + require.Equal(t, ttl, ttlResp.GrantedTTL) + }) + }) + } +} + +func TestLeaseGrantAndList(t *testing.T) { + testRunner.BeforeTest(t) + + tcs := []struct { + name string + config config.ClusterConfig + }{ + { + name: "NoTLS", + config: config.ClusterConfig{ClusterSize: 1}, + }, + { + name: "PeerTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS}, + }, + { + name: "PeerAutoTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS}, + }, + { + name: "ClientTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS}, + }, + { + name: "ClientAutoTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS}, + }, + } + for _, tc := range tcs { + nestedCases := []struct { + name string + leaseCount int + }{ + { + name: "no_leases", + leaseCount: 0, + }, + { + name: "one_lease", + leaseCount: 1, + }, + { + name: "many_leases", + leaseCount: 3, + }, + } + + for _, nc := range nestedCases { + t.Run(tc.name+"/"+nc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, tc.config) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + createdLeases := []clientv3.LeaseID{} + for i := 0; i < nc.leaseCount; i++ { + leaseResp, err := cc.Grant(10) + require.NoError(t, err) + createdLeases = append(createdLeases, leaseResp.ID) + } + + resp, err := cc.LeaseList() + require.NoError(t, err) + require.Len(t, resp.Leases, nc.leaseCount) + + returnedLeases := make([]clientv3.LeaseID, 0, nc.leaseCount) + for _, status := range resp.Leases { + returnedLeases = append(returnedLeases, status.ID) + } + + require.ElementsMatch(t, createdLeases, returnedLeases) + }) + }) + } + } +} + +func TestLeaseGrantTimeToLiveExpired(t *testing.T) { + testRunner.BeforeTest(t) + + tcs := []struct { + name string + config config.ClusterConfig + }{ + { + name: "NoTLS", + config: config.ClusterConfig{ClusterSize: 1}, + }, + { + name: "PeerTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS}, + }, + { + name: "PeerAutoTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS}, + }, + { + name: "ClientTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS}, + }, + { + name: "ClientAutoTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, tc.config) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + leaseResp, err := cc.Grant(2) + require.NoError(t, err) + + err = cc.Put("foo", "bar", config.PutOptions{LeaseID: leaseResp.ID}) + require.NoError(t, err) + + getResp, err := cc.Get("foo", config.GetOptions{}) + require.NoError(t, err) + require.Equal(t, int64(1), getResp.Count) + + time.Sleep(3 * time.Second) + + ttlResp, err := cc.TimeToLive(leaseResp.ID, config.LeaseOption{}) + require.NoError(t, err) + require.Equal(t, int64(-1), ttlResp.TTL) + + getResp, err = cc.Get("foo", config.GetOptions{}) + require.NoError(t, err) + // Value should expire with the lease + require.Equal(t, int64(0), getResp.Count) + }) + }) + } +} + +func TestLeaseGrantKeepAliveOnce(t *testing.T) { + testRunner.BeforeTest(t) + + tcs := []struct { + name string + config config.ClusterConfig + }{ + { + name: "NoTLS", + config: config.ClusterConfig{ClusterSize: 1}, + }, + { + name: "PeerTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS}, + }, + { + name: "PeerAutoTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS}, + }, + { + name: "ClientTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS}, + }, + { + name: "ClientAutoTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, tc.config) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + leaseResp, err := cc.Grant(2) + require.NoError(t, err) + + _, err = cc.LeaseKeepAliveOnce(leaseResp.ID) + require.NoError(t, err) + + time.Sleep(2 * time.Second) // Wait for the original lease to expire + + ttlResp, err := cc.TimeToLive(leaseResp.ID, config.LeaseOption{}) + require.NoError(t, err) + // We still have a lease! + require.Greater(t, int64(2), ttlResp.TTL) + }) + }) + } +} + +func TestLeaseGrantRevoke(t *testing.T) { + testRunner.BeforeTest(t) + + tcs := []struct { + name string + config config.ClusterConfig + }{ + { + name: "NoTLS", + config: config.ClusterConfig{ClusterSize: 1}, + }, + { + name: "PeerTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.ManualTLS}, + }, + { + name: "PeerAutoTLS", + config: config.ClusterConfig{ClusterSize: 3, PeerTLS: config.AutoTLS}, + }, + { + name: "ClientTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.ManualTLS}, + }, + { + name: "ClientAutoTLS", + config: config.ClusterConfig{ClusterSize: 1, ClientTLS: config.AutoTLS}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, tc.config) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + leaseResp, err := cc.Grant(20) + require.NoError(t, err) + + err = cc.Put("foo", "bar", config.PutOptions{LeaseID: leaseResp.ID}) + require.NoError(t, err) + + getResp, err := cc.Get("foo", config.GetOptions{}) + require.NoError(t, err) + require.Equal(t, int64(1), getResp.Count) + + _, err = cc.LeaseRevoke(leaseResp.ID) + require.NoError(t, err) + + ttlResp, err := cc.TimeToLive(leaseResp.ID, config.LeaseOption{}) + require.NoError(t, err) + require.Equal(t, int64(-1), ttlResp.TTL) + + getResp, err = cc.Get("foo", config.GetOptions{}) + require.NoError(t, err) + // Value should expire with the lease + require.Equal(t, int64(0), getResp.Count) + }) + }) + } +} diff --git a/tests/e2e/ctl_v3_auth_test.go b/tests/e2e/ctl_v3_auth_test.go index fd15a9e617e..6c75f5b0634 100644 --- a/tests/e2e/ctl_v3_auth_test.go +++ b/tests/e2e/ctl_v3_auth_test.go @@ -853,6 +853,28 @@ func authLeaseTestTimeToLiveExpired(cx ctlCtx) { } } +func leaseTestTimeToLiveExpire(cx ctlCtx, ttl int) error { + leaseID, err := ctlV3LeaseGrant(cx, ttl) + if err != nil { + return fmt.Errorf("ctlV3LeaseGrant error (%v)", err) + } + + if err = ctlV3Put(cx, "key", "val", leaseID); err != nil { + return fmt.Errorf("ctlV3Put error (%v)", err) + } + // eliminate false positive + time.Sleep(time.Duration(ttl+1) * time.Second) + cmdArgs := append(cx.PrefixArgs(), "lease", "timetolive", leaseID) + exp := fmt.Sprintf("lease %s already expired", leaseID) + if err = e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, exp); err != nil { + return fmt.Errorf("lease not properly expired: (%v)", err) + } + if err := ctlV3Get(cx, []string{"key"}); err != nil { + return fmt.Errorf("ctlV3Get error (%v)", err) + } + return nil +} + func authLeaseTestLeaseGrantLeases(cx ctlCtx) { cx.user, cx.pass = "root", "root" authSetupTestUser(cx) @@ -862,6 +884,24 @@ func authLeaseTestLeaseGrantLeases(cx ctlCtx) { } } +func leaseTestGrantLeasesList(cx ctlCtx) error { + id, err := ctlV3LeaseGrant(cx, 10) + if err != nil { + return fmt.Errorf("ctlV3LeaseGrant error (%v)", err) + } + + cmdArgs := append(cx.PrefixArgs(), "lease", "list") + proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap) + if err != nil { + return fmt.Errorf("lease list failed (%v)", err) + } + _, err = proc.Expect(id) + if err != nil { + return fmt.Errorf("lease id not in returned list (%v)", err) + } + return proc.Close() +} + func authLeaseTestLeaseRevoke(cx ctlCtx) { cx.user, cx.pass = "root", "root" authSetupTestUser(cx) diff --git a/tests/e2e/ctl_v3_grpc_test.go b/tests/e2e/ctl_v3_grpc_test.go index 8c8be2c698e..878dd794828 100644 --- a/tests/e2e/ctl_v3_grpc_test.go +++ b/tests/e2e/ctl_v3_grpc_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/e2e" "go.etcd.io/etcd/tests/v3/framework/testutils" ) @@ -100,7 +101,7 @@ func TestAuthority(t *testing.T) { endpoints := templateEndpoints(t, tc.clientURLPattern, epc) client := e2e.NewEtcdctl(cfg, endpoints) - err = client.Put("foo", "bar") + err = client.Put("foo", "bar", config.PutOptions{}) if err != nil { t.Fatal(err) } diff --git a/tests/e2e/ctl_v3_lease_test.go b/tests/e2e/ctl_v3_lease_test.go index d13309bc5bd..6ac44c44f24 100644 --- a/tests/e2e/ctl_v3_lease_test.go +++ b/tests/e2e/ctl_v3_lease_test.go @@ -19,53 +19,10 @@ import ( "strconv" "strings" "testing" - "time" "go.etcd.io/etcd/tests/v3/framework/e2e" ) -func TestCtlV3LeaseGrantTimeToLive(t *testing.T) { testCtl(t, leaseTestGrantTimeToLive) } -func TestCtlV3LeaseGrantTimeToLiveNoTLS(t *testing.T) { - testCtl(t, leaseTestGrantTimeToLive, withCfg(*e2e.NewConfigNoTLS())) -} -func TestCtlV3LeaseGrantTimeToLiveClientTLS(t *testing.T) { - testCtl(t, leaseTestGrantTimeToLive, withCfg(*e2e.NewConfigClientTLS())) -} -func TestCtlV3LeaseGrantTimeToLiveClientAutoTLS(t *testing.T) { - testCtl(t, leaseTestGrantTimeToLive, withCfg(*e2e.NewConfigClientAutoTLS())) -} -func TestCtlV3LeaseGrantTimeToLivePeerTLS(t *testing.T) { - testCtl(t, leaseTestGrantTimeToLive, withCfg(*e2e.NewConfigPeerTLS())) -} - -func TestCtlV3LeaseGrantLeases(t *testing.T) { testCtl(t, leaseTestGrantLeaseListed) } -func TestCtlV3LeaseGrantLeasesNoTLS(t *testing.T) { - testCtl(t, leaseTestGrantLeaseListed, withCfg(*e2e.NewConfigNoTLS())) -} -func TestCtlV3LeaseGrantLeasesClientTLS(t *testing.T) { - testCtl(t, leaseTestGrantLeaseListed, withCfg(*e2e.NewConfigClientTLS())) -} -func TestCtlV3LeaseGrantLeasesClientAutoTLS(t *testing.T) { - testCtl(t, leaseTestGrantLeaseListed, withCfg(*e2e.NewConfigClientAutoTLS())) -} -func TestCtlV3LeaseGrantLeasesPeerTLS(t *testing.T) { - testCtl(t, leaseTestGrantLeaseListed, withCfg(*e2e.NewConfigPeerTLS())) -} - -func TestCtlV3LeaseTestTimeToLiveExpired(t *testing.T) { testCtl(t, leaseTestTimeToLiveExpired) } -func TestCtlV3LeaseTestTimeToLiveExpiredNoTLS(t *testing.T) { - testCtl(t, leaseTestTimeToLiveExpired, withCfg(*e2e.NewConfigNoTLS())) -} -func TestCtlV3LeaseTestTimeToLiveExpiredClientTLS(t *testing.T) { - testCtl(t, leaseTestTimeToLiveExpired, withCfg(*e2e.NewConfigClientTLS())) -} -func TestCtlV3LeaseTestTimeToLiveExpiredClientAutoTLS(t *testing.T) { - testCtl(t, leaseTestTimeToLiveExpired, withCfg(*e2e.NewConfigClientAutoTLS())) -} -func TestCtlV3LeaseTestTimeToLiveExpiredPeerTLS(t *testing.T) { - testCtl(t, leaseTestTimeToLiveExpired, withCfg(*e2e.NewConfigPeerTLS())) -} - func TestCtlV3LeaseKeepAlive(t *testing.T) { testCtl(t, leaseTestKeepAlive) } func TestCtlV3LeaseKeepAliveNoTLS(t *testing.T) { testCtl(t, leaseTestKeepAlive, withCfg(*e2e.NewConfigNoTLS())) @@ -80,114 +37,6 @@ func TestCtlV3LeaseKeepAlivePeerTLS(t *testing.T) { testCtl(t, leaseTestKeepAlive, withCfg(*e2e.NewConfigPeerTLS())) } -func TestCtlV3LeaseKeepAliveOnce(t *testing.T) { testCtl(t, leaseTestKeepAliveOnce) } -func TestCtlV3LeaseKeepAliveOnceNoTLS(t *testing.T) { - testCtl(t, leaseTestKeepAliveOnce, withCfg(*e2e.NewConfigNoTLS())) -} -func TestCtlV3LeaseKeepAliveOnceClientTLS(t *testing.T) { - testCtl(t, leaseTestKeepAliveOnce, withCfg(*e2e.NewConfigClientTLS())) -} -func TestCtlV3LeaseKeepAliveOnceClientAutoTLS(t *testing.T) { - testCtl(t, leaseTestKeepAliveOnce, withCfg(*e2e.NewConfigClientAutoTLS())) -} -func TestCtlV3LeaseKeepAliveOncePeerTLS(t *testing.T) { - testCtl(t, leaseTestKeepAliveOnce, withCfg(*e2e.NewConfigPeerTLS())) -} - -func TestCtlV3LeaseRevoke(t *testing.T) { testCtl(t, leaseTestRevoked) } -func TestCtlV3LeaseRevokeNoTLS(t *testing.T) { - testCtl(t, leaseTestRevoked, withCfg(*e2e.NewConfigNoTLS())) -} -func TestCtlV3LeaseRevokeClientTLS(t *testing.T) { - testCtl(t, leaseTestRevoked, withCfg(*e2e.NewConfigClientTLS())) -} -func TestCtlV3LeaseRevokeClientAutoTLS(t *testing.T) { - testCtl(t, leaseTestRevoked, withCfg(*e2e.NewConfigClientAutoTLS())) -} -func TestCtlV3LeaseRevokePeerTLS(t *testing.T) { - testCtl(t, leaseTestRevoked, withCfg(*e2e.NewConfigPeerTLS())) -} - -func leaseTestGrantTimeToLive(cx ctlCtx) { - id, err := ctlV3LeaseGrant(cx, 10) - if err != nil { - cx.t.Fatalf("leaseTestGrantTimeToLive: ctlV3LeaseGrant error (%v)", err) - } - - cmdArgs := append(cx.PrefixArgs(), "lease", "timetolive", id, "--keys") - proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap) - if err != nil { - cx.t.Fatalf("leaseTestGrantTimeToLive: error (%v)", err) - } - line, err := proc.Expect(" granted with TTL(") - if err != nil { - cx.t.Fatalf("leaseTestGrantTimeToLive: error (%v)", err) - } - if err = proc.Close(); err != nil { - cx.t.Fatalf("leaseTestGrantTimeToLive: error (%v)", err) - } - if !strings.Contains(line, ", attached keys") { - cx.t.Fatalf("leaseTestGrantTimeToLive: expected 'attached keys', got %q", line) - } - if !strings.Contains(line, id) { - cx.t.Fatalf("leaseTestGrantTimeToLive: expected leaseID %q, got %q", id, line) - } -} - -func leaseTestGrantLeaseListed(cx ctlCtx) { - err := leaseTestGrantLeasesList(cx) - if err != nil { - cx.t.Fatalf("leaseTestGrantLeasesList: (%v)", err) - } -} - -func leaseTestGrantLeasesList(cx ctlCtx) error { - id, err := ctlV3LeaseGrant(cx, 10) - if err != nil { - return fmt.Errorf("ctlV3LeaseGrant error (%v)", err) - } - - cmdArgs := append(cx.PrefixArgs(), "lease", "list") - proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap) - if err != nil { - return fmt.Errorf("lease list failed (%v)", err) - } - _, err = proc.Expect(id) - if err != nil { - return fmt.Errorf("lease id not in returned list (%v)", err) - } - return proc.Close() -} - -func leaseTestTimeToLiveExpired(cx ctlCtx) { - err := leaseTestTimeToLiveExpire(cx, 3) - if err != nil { - cx.t.Fatalf("leaseTestTimeToLiveExpire: (%v)", err) - } -} - -func leaseTestTimeToLiveExpire(cx ctlCtx, ttl int) error { - leaseID, err := ctlV3LeaseGrant(cx, ttl) - if err != nil { - return fmt.Errorf("ctlV3LeaseGrant error (%v)", err) - } - - if err = ctlV3Put(cx, "key", "val", leaseID); err != nil { - return fmt.Errorf("ctlV3Put error (%v)", err) - } - // eliminate false positive - time.Sleep(time.Duration(ttl+1) * time.Second) - cmdArgs := append(cx.PrefixArgs(), "lease", "timetolive", leaseID) - exp := fmt.Sprintf("lease %s already expired", leaseID) - if err = e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, exp); err != nil { - return fmt.Errorf("lease not properly expired: (%v)", err) - } - if err := ctlV3Get(cx, []string{"key"}); err != nil { - return fmt.Errorf("ctlV3Get error (%v)", err) - } - return nil -} - func leaseTestKeepAlive(cx ctlCtx) { // put with TTL 10 seconds and keep-alive leaseID, err := ctlV3LeaseGrant(cx, 10) @@ -205,48 +54,6 @@ func leaseTestKeepAlive(cx ctlCtx) { } } -func leaseTestKeepAliveOnce(cx ctlCtx) { - // put with TTL 10 seconds and keep-alive once - leaseID, err := ctlV3LeaseGrant(cx, 10) - if err != nil { - cx.t.Fatalf("leaseTestKeepAlive: ctlV3LeaseGrant error (%v)", err) - } - if err := ctlV3Put(cx, "key", "val", leaseID); err != nil { - cx.t.Fatalf("leaseTestKeepAlive: ctlV3Put error (%v)", err) - } - if err := ctlV3LeaseKeepAliveOnce(cx, leaseID); err != nil { - cx.t.Fatalf("leaseTestKeepAlive: ctlV3LeaseKeepAliveOnce error (%v)", err) - } - if err := ctlV3Get(cx, []string{"key"}, kv{"key", "val"}); err != nil { - cx.t.Fatalf("leaseTestKeepAlive: ctlV3Get error (%v)", err) - } -} - -func leaseTestRevoked(cx ctlCtx) { - err := leaseTestRevoke(cx) - if err != nil { - cx.t.Fatalf("leaseTestRevoke: (%v)", err) - } -} - -func leaseTestRevoke(cx ctlCtx) error { - // put with TTL 10 seconds and revoke - leaseID, err := ctlV3LeaseGrant(cx, 10) - if err != nil { - return fmt.Errorf("ctlV3LeaseGrant error (%v)", err) - } - if err := ctlV3Put(cx, "key", "val", leaseID); err != nil { - return fmt.Errorf("ctlV3Put error (%v)", err) - } - if err := ctlV3LeaseRevoke(cx, leaseID); err != nil { - return fmt.Errorf("ctlV3LeaseRevoke error (%v)", err) - } - if err := ctlV3Get(cx, []string{"key"}); err != nil { // expect no output - return fmt.Errorf("ctlV3Get error (%v)", err) - } - return nil -} - func ctlV3LeaseGrant(cx ctlCtx, ttl int) (string, error) { cmdArgs := append(cx.PrefixArgs(), "lease", "grant", strconv.Itoa(ttl)) proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap) @@ -284,20 +91,6 @@ func ctlV3LeaseKeepAlive(cx ctlCtx, leaseID string) error { return proc.Stop() } -func ctlV3LeaseKeepAliveOnce(cx ctlCtx, leaseID string) error { - cmdArgs := append(cx.PrefixArgs(), "lease", "keep-alive", "--once", leaseID) - - proc, err := e2e.SpawnCmd(cmdArgs, nil) - if err != nil { - return err - } - - if _, err = proc.Expect(fmt.Sprintf("lease %s keepalived with TTL(", leaseID)); err != nil { - return err - } - return proc.Stop() -} - func ctlV3LeaseRevoke(cx ctlCtx, leaseID string) error { cmdArgs := append(cx.PrefixArgs(), "lease", "revoke", leaseID) return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("lease %s revoked", leaseID)) diff --git a/tests/framework/config/client.go b/tests/framework/config/client.go index 6160d841da7..ef2ef9c273c 100644 --- a/tests/framework/config/client.go +++ b/tests/framework/config/client.go @@ -32,6 +32,10 @@ type GetOptions struct { SortBy clientv3.SortTarget } +type PutOptions struct { + LeaseID clientv3.LeaseID +} + type DeleteOptions struct { Prefix bool FromKey bool @@ -46,3 +50,7 @@ type CompactOption struct { type DefragOption struct { Timeout time.Duration } + +type LeaseOption struct { + WithAttachedKeys bool +} diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index f11bdceadfc..546c9c30510 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -17,6 +17,7 @@ package e2e import ( "encoding/json" "fmt" + "strconv" "strings" clientv3 "go.etcd.io/etcd/client/v3" @@ -106,8 +107,13 @@ func (ctl *EtcdctlV3) Get(key string, o config.GetOptions) (*clientv3.GetRespons return &resp, err } -func (ctl *EtcdctlV3) Put(key, value string) error { - return SpawnWithExpect(ctl.cmdArgs("put", key, value), "OK") +func (ctl *EtcdctlV3) Put(key, value string, opts config.PutOptions) error { + args := ctl.cmdArgs() + args = append(args, "put", key, value) + if opts.LeaseID != 0 { + args = append(args, "--lease", strconv.FormatInt(int64(opts.LeaseID), 16)) + } + return SpawnWithExpect(args, "OK") } func (ctl *EtcdctlV3) Delete(key string, o config.DeleteOptions) (*clientv3.DeleteResponse, error) { @@ -185,7 +191,6 @@ func (ctl *EtcdctlV3) Status() ([]*clientv3.StatusResponse, error) { if err != nil { return nil, err } - var epStatus []*struct { Endpoint string Status *clientv3.StatusResponse @@ -241,11 +246,44 @@ func (ctl *EtcdctlV3) Health() error { lines[i] = "is healthy" } return SpawnWithExpects(args, map[string]string{}, lines...) +} +func (ctl *EtcdctlV3) Grant(ttl int64) (*clientv3.LeaseGrantResponse, error) { + args := ctl.cmdArgs() + args = append(args, "lease", "grant", strconv.FormatInt(ttl, 10), "-w", "json") + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp clientv3.LeaseGrantResponse + line, err := cmd.Expect("ID") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return &resp, err } -func (ctl *EtcdctlV3) Defragment(o config.DefragOption) error { +func (ctl *EtcdctlV3) TimeToLive(id clientv3.LeaseID, o config.LeaseOption) (*clientv3.LeaseTimeToLiveResponse, error) { + args := ctl.cmdArgs() + args = append(args, "lease", "timetolive", strconv.FormatInt(int64(id), 16), "-w", "json") + if o.WithAttachedKeys { + args = append(args, "--keys") + } + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp clientv3.LeaseTimeToLiveResponse + line, err := cmd.Expect("id") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return &resp, err +} +func (ctl *EtcdctlV3) Defragment(o config.DefragOption) error { args := append(ctl.cmdArgs(), "defrag") if o.Timeout != 0 { args = append(args, fmt.Sprintf("--command-timeout=%s", o.Timeout)) @@ -257,3 +295,51 @@ func (ctl *EtcdctlV3) Defragment(o config.DefragOption) error { _, err := SpawnWithExpectLines(args, map[string]string{}, lines...) return err } + +func (ctl *EtcdctlV3) LeaseList() (*clientv3.LeaseLeasesResponse, error) { + args := ctl.cmdArgs() + args = append(args, "lease", "list", "-w", "json") + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp clientv3.LeaseLeasesResponse + line, err := cmd.Expect("id") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return &resp, err +} + +func (ctl *EtcdctlV3) LeaseKeepAliveOnce(id clientv3.LeaseID) (*clientv3.LeaseKeepAliveResponse, error) { + args := ctl.cmdArgs() + args = append(args, "lease", "keep-alive", strconv.FormatInt(int64(id), 16), "--once", "-w", "json") + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp clientv3.LeaseKeepAliveResponse + line, err := cmd.Expect("ID") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return &resp, err +} + +func (ctl *EtcdctlV3) LeaseRevoke(id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error) { + args := ctl.cmdArgs() + args = append(args, "lease", "revoke", strconv.FormatInt(int64(id), 16), "-w", "json") + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp clientv3.LeaseRevokeResponse + line, err := cmd.Expect("header") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return &resp, err +} diff --git a/tests/framework/integration.go b/tests/framework/integration.go index 94c1925b7c4..28bb414220e 100644 --- a/tests/framework/integration.go +++ b/tests/framework/integration.go @@ -126,8 +126,12 @@ func (c integrationClient) Get(key string, o config.GetOptions) (*clientv3.GetRe return c.Client.Get(context.Background(), key, clientOpts...) } -func (c integrationClient) Put(key, value string) error { - _, err := c.Client.Put(context.Background(), key, value) +func (c integrationClient) Put(key, value string, opts config.PutOptions) error { + clientOpts := []clientv3.OpOption{} + if opts.LeaseID != 0 { + clientOpts = append(clientOpts, clientv3.WithLease(opts.LeaseID)) + } + _, err := c.Client.Put(context.Background(), key, value, clientOpts...) return err } @@ -212,3 +216,37 @@ func (c integrationClient) Defragment(o config.DefragOption) error { } return nil } + +func (c integrationClient) Grant(ttl int64) (*clientv3.LeaseGrantResponse, error) { + ctx := context.Background() + return c.Client.Grant(ctx, ttl) +} + +func (c integrationClient) TimeToLive(id clientv3.LeaseID, o config.LeaseOption) (*clientv3.LeaseTimeToLiveResponse, error) { + ctx := context.Background() + + leaseOpts := []clientv3.LeaseOption{} + if o.WithAttachedKeys { + leaseOpts = append(leaseOpts, clientv3.WithAttachedKeys()) + } + + return c.Client.TimeToLive(ctx, id, leaseOpts...) +} + +func (c integrationClient) LeaseList() (*clientv3.LeaseLeasesResponse, error) { + ctx := context.Background() + + return c.Client.Leases(ctx) +} + +func (c integrationClient) LeaseKeepAliveOnce(id clientv3.LeaseID) (*clientv3.LeaseKeepAliveResponse, error) { + ctx := context.Background() + + return c.Client.KeepAliveOnce(ctx, id) +} + +func (c integrationClient) LeaseRevoke(id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error) { + ctx := context.Background() + + return c.Client.Revoke(ctx, id) +} diff --git a/tests/framework/interface.go b/tests/framework/interface.go index db4b3b980cc..cde4c8a3a72 100644 --- a/tests/framework/interface.go +++ b/tests/framework/interface.go @@ -33,13 +33,17 @@ type Cluster interface { } type Client interface { - Put(key, value string) error + Put(key, value string, opts config.PutOptions) error Get(key string, opts config.GetOptions) (*clientv3.GetResponse, error) Delete(key string, opts config.DeleteOptions) (*clientv3.DeleteResponse, error) Compact(rev int64, opts config.CompactOption) (*clientv3.CompactResponse, error) - Status() ([]*clientv3.StatusResponse, error) HashKV(rev int64) ([]*clientv3.HashKVResponse, error) Health() error Defragment(opts config.DefragOption) error + Grant(ttl int64) (*clientv3.LeaseGrantResponse, error) + TimeToLive(id clientv3.LeaseID, opts config.LeaseOption) (*clientv3.LeaseTimeToLiveResponse, error) + LeaseList() (*clientv3.LeaseLeasesResponse, error) + LeaseKeepAliveOnce(id clientv3.LeaseID) (*clientv3.LeaseKeepAliveResponse, error) + LeaseRevoke(id clientv3.LeaseID) (*clientv3.LeaseRevokeResponse, error) }