diff --git a/tests/common/endpoint_test.go b/tests/common/endpoint_test.go new file mode 100644 index 000000000000..872d11fb606a --- /dev/null +++ b/tests/common/endpoint_test.go @@ -0,0 +1,96 @@ +// 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/assert" + "go.etcd.io/etcd/tests/v3/framework/config" + "go.etcd.io/etcd/tests/v3/framework/testutils" +) + +func TestEndpointStatus(t *testing.T) { + testRunner.BeforeTest(t) + tcs := []struct { + name string + }{ + { + name: "OK", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 3}) + defer clus.Close() + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + _, err := clus.Client().Status() + if err != nil { + t.Fatalf("get endpoint status error: %v", err) + } + }) + }) + } +} + +func TestEndpointHashKV(t *testing.T) { + testRunner.BeforeTest(t) + tcs := []struct { + name string + }{ + { + name: "OK", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 3}) + defer clus.Close() + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + _, err := clus.Client().HashKV(0) + if err != nil { + t.Fatalf("get endpoint hashkv error: %v", err) + } + }) + }) + } +} + +func TestEndpointHealth(t *testing.T) { + testRunner.BeforeTest(t) + tcs := []struct { + name string + }{ + { + name: "OK", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 3}) + defer clus.Close() + testutils.ExecuteWithTimeout(t, 10*time.Second, func() { + epHealths, err := clus.Client().Health() + if err != nil { + t.Fatalf("get endpoint health error: %v", err) + } + for _, health := range epHealths { + assert.Equal(t, true, health.Health) + } + }) + }) + } +} diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index f807c7fe67d1..4219859ce2bc 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -19,6 +19,8 @@ import ( "fmt" "strings" + "go.etcd.io/etcd/tests/v3/schema" + clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/tests/v3/framework/config" ) @@ -176,3 +178,55 @@ func (ctl *EtcdctlV3) Compact(rev int64, o config.CompactOption) (*clientv3.Comp return nil, SpawnWithExpect(args, fmt.Sprintf("compacted revision %v", rev)) } + +func (ctl *EtcdctlV3) Status() ([]*schema.EndpointStatus, error) { + args := ctl.cmdArgs() + args = append(args, "endpoint", "status", "-w", "json") + args = append(args, "--endpoints", strings.Join(ctl.endpoints, ",")) + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp []*schema.EndpointStatus + line, err := cmd.Expect("header") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return resp, err +} + +func (ctl *EtcdctlV3) HashKV(rev int64) ([]*schema.EndpointHashKV, error) { + args := ctl.cmdArgs() + args = append(args, "endpoint", "hashkv", "-w", "json") + args = append(args, "--endpoints", strings.Join(ctl.endpoints, ",")) + args = append(args, "--rev", fmt.Sprint(rev)) + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp []*schema.EndpointHashKV + line, err := cmd.Expect("header") + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(line), &resp) + return resp, err +} + +func (ctl *EtcdctlV3) Health() ([]*schema.EndpointHealth, error) { + args := ctl.cmdArgs() + args = append(args, "endpoint", "health", "-w", "json") + args = append(args, "--endpoints", strings.Join(ctl.endpoints, ",")) + cmd, err := SpawnCmd(args, nil) + if err != nil { + return nil, err + } + var resp []*schema.EndpointHealth + line, err := cmd.Expect("health") + 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 18aabed5a334..aec75a69ae41 100644 --- a/tests/framework/integration.go +++ b/tests/framework/integration.go @@ -18,10 +18,15 @@ import ( "context" "fmt" "testing" + "time" + "go.etcd.io/etcd/tests/v3/schema" + + "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" "go.etcd.io/etcd/client/pkg/v3/testutil" "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/config" "go.etcd.io/etcd/tests/v3/framework/integration" "go.uber.org/zap" @@ -155,3 +160,47 @@ func (c integrationClient) Compact(rev int64, o config.CompactOption) (*clientv3 } return c.Client.Compact(ctx, rev, clientOpts...) } + +func (c integrationClient) Status() ([]*schema.EndpointStatus, error) { + endpoints := c.Client.Endpoints() + var resp []*schema.EndpointStatus + for _, ep := range endpoints { + status, err := c.Client.Status(context.Background(), ep) + if err != nil { + return nil, err + } + resp = append(resp, &schema.EndpointStatus{Endpoint: ep, Status: status}) + } + return resp, nil +} + +func (c integrationClient) HashKV(rev int64) ([]*schema.EndpointHashKV, error) { + endpoints := c.Client.Endpoints() + var resp []*schema.EndpointHashKV + for _, ep := range endpoints { + hashKV, err := c.Client.HashKV(context.Background(), ep, rev) + if err != nil { + return nil, err + } + resp = append(resp, &schema.EndpointHashKV{Endpoint: ep, HashKV: hashKV}) + } + return resp, nil +} + +func (c integrationClient) Health() ([]*schema.EndpointHealth, error) { + endpoints := c.Client.Endpoints() + var resp []*schema.EndpointHealth + for _, ep := range endpoints { + st := time.Now() + _, err := c.Client.Get(context.Background(), "health") + eh := schema.EndpointHealth{Endpoint: ep, Health: false, Took: time.Since(st).String()} + // permission denied is OK since proposal goes through consensus to get it + if err == nil || err == rpctypes.ErrPermissionDenied { + eh.Health = true + } else { + eh.Error = err.Error() + } + resp = append(resp, &eh) + } + return resp, nil +} diff --git a/tests/framework/interface.go b/tests/framework/interface.go index 5c1ac01145a6..87b936191f42 100644 --- a/tests/framework/interface.go +++ b/tests/framework/interface.go @@ -17,6 +17,8 @@ package framework import ( "testing" + "go.etcd.io/etcd/tests/v3/schema" + clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/tests/v3/framework/config" ) @@ -37,4 +39,8 @@ type Client interface { 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() ([]*schema.EndpointStatus, error) + HashKV(rev int64) ([]*schema.EndpointHashKV, error) + Health() ([]*schema.EndpointHealth, error) } diff --git a/tests/schema/schema.go b/tests/schema/schema.go new file mode 100644 index 000000000000..d00475cc0d8a --- /dev/null +++ b/tests/schema/schema.go @@ -0,0 +1,22 @@ +package schema + +import ( + clientv3 "go.etcd.io/etcd/client/v3" +) + +type EndpointStatus struct { + Endpoint string + Status *clientv3.StatusResponse +} + +type EndpointHashKV struct { + Endpoint string + HashKV *clientv3.HashKVResponse +} + +type EndpointHealth struct { + Endpoint string `json:"endpoint"` + Health bool `json:"health"` + Took string `json:"took"` + Error string `json:"error,omitempty"` +}