Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migrate e2e & integration role_test to common #14020

Merged
merged 1 commit into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions tests/common/role_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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 (
"strings"
"testing"
"time"

"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/tests/v3/framework/config"
"go.etcd.io/etcd/tests/v3/framework/testutils"
)

func TestRoleAdd_Simple(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() {
_, err := cc.RoleAdd("root")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
})
})
}
}

func TestRoleAdd_Error(t *testing.T) {
testRunner.BeforeTest(t)
clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 1})
defer clus.Close()
cc := clus.Client()
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
_, err := cc.RoleAdd("test-role")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
_, err = cc.RoleAdd("test-role")
if err == nil || !strings.Contains(err.Error(), rpctypes.ErrRoleAlreadyExist.Error()) {
t.Fatalf("want (%v) error, but got (%v)", rpctypes.ErrRoleAlreadyExist, err)
}
_, err = cc.RoleAdd("")
if err == nil || !strings.Contains(err.Error(), rpctypes.ErrRoleEmpty.Error()) {
t.Fatalf("want (%v) error, but got (%v)", rpctypes.ErrRoleEmpty, err)
}
})
}

func TestRootRole(t *testing.T) {
testRunner.BeforeTest(t)
clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 1})
defer clus.Close()
cc := clus.Client()
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
_, err := cc.RoleAdd("root")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
resp, err := cc.RoleGet("root")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
t.Logf("get role resp %+v", resp)
// granting to root should be refused by server and a no-op
_, err = cc.RoleGrantPermission("root", "foo", "", clientv3.PermissionType(clientv3.PermReadWrite))
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
resp2, err := cc.RoleGet("root")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
t.Logf("get role resp %+v", resp2)
})
}

func TestRoleGrantRevokePermission(t *testing.T) {
testRunner.BeforeTest(t)
clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 1})
defer clus.Close()
cc := clus.Client()
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
_, err := cc.RoleAdd("role1")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
_, err = cc.RoleGrantPermission("role1", "bar", "", clientv3.PermissionType(clientv3.PermRead))
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
_, err = cc.RoleGrantPermission("role1", "bar", "", clientv3.PermissionType(clientv3.PermWrite))
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
_, err = cc.RoleGrantPermission("role1", "bar", "foo", clientv3.PermissionType(clientv3.PermReadWrite))
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
_, err = cc.RoleRevokePermission("role1", "foo", "")
if err == nil || !strings.Contains(err.Error(), rpctypes.ErrPermissionNotGranted.Error()) {
t.Fatalf("want error (%v), but got (%v)", rpctypes.ErrPermissionNotGranted, err)
}
_, err = cc.RoleRevokePermission("role1", "bar", "foo")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
})
}

func TestRoleDelete(t *testing.T) {
testRunner.BeforeTest(t)
clus := testRunner.NewCluster(t, config.ClusterConfig{ClusterSize: 1})
defer clus.Close()
cc := clus.Client()
testutils.ExecuteWithTimeout(t, 10*time.Second, func() {
_, err := cc.RoleAdd("role1")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
_, err = cc.RoleDelete("role1")
if err != nil {
t.Fatalf("want no error, but got (%v)", err)
}
})
}
99 changes: 2 additions & 97 deletions tests/e2e/ctl_v3_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,10 @@ import (
"go.etcd.io/etcd/tests/v3/framework/e2e"
)

func TestCtlV3RoleAdd(t *testing.T) { testCtl(t, roleAddTest) }
func TestCtlV3RootRoleGet(t *testing.T) { testCtl(t, rootRoleGetTest) }
func TestCtlV3RoleAddNoTLS(t *testing.T) { testCtl(t, roleAddTest, withCfg(*e2e.NewConfigNoTLS())) }
func TestCtlV3RoleAddClientTLS(t *testing.T) {
testCtl(t, roleAddTest, withCfg(*e2e.NewConfigClientTLS()))
}
func TestCtlV3RoleAddPeerTLS(t *testing.T) { testCtl(t, roleAddTest, withCfg(*e2e.NewConfigPeerTLS())) }
// TestCtlV3RoleAddTimeout tests add role with 0 grpc dial timeout while it tolerates dial timeout error.
// This is unique in e2e test
func TestCtlV3RoleAddTimeout(t *testing.T) { testCtl(t, roleAddTest, withDialTimeout(0)) }
chaochn47 marked this conversation as resolved.
Show resolved Hide resolved

func TestCtlV3RoleGrant(t *testing.T) { testCtl(t, roleGrantTest) }

func roleAddTest(cx ctlCtx) {
Copy link
Member Author

@chaochn47 chaochn47 May 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Called from TestCtlV3RoleAddTimeout. So it was left un-deleted.

cmdSet := []struct {
args []string
Expand All @@ -58,94 +51,6 @@ func roleAddTest(cx ctlCtx) {
}
}

func rootRoleGetTest(cx ctlCtx) {
cmdSet := []struct {
args []string
expectedStr interface{}
}{
// Add a role of root .
{
args: []string{"add", "root"},
expectedStr: "Role root created",
},
// get root role should always return [, <open ended>
{
args: []string{"get", "root"},
expectedStr: []string{"Role root\r\n", "KV Read:\r\n", "\t[, <open ended>\r\n", "KV Write:\r\n", "\t[, <open ended>\r\n"},
},
// granting to root should be refused by server
{
args: []string{"grant-permission", "root", "readwrite", "foo"},
expectedStr: "Role root updated",
},
{
args: []string{"get", "root"},
expectedStr: []string{"Role root\r\n", "KV Read:\r\n", "\t[, <open ended>\r\n", "KV Write:\r\n", "\t[, <open ended>\r\n"},
},
}

for i, cmd := range cmdSet {
if _, ok := cmd.expectedStr.(string); ok {
if err := ctlV3Role(cx, cmd.args, cmd.expectedStr.(string)); err != nil {
if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
cx.t.Fatalf("roleAddTest #%d: ctlV3Role error (%v)", i, err)
}
}
} else {
if err := ctlV3RoleMultiExpect(cx, cmd.args, cmd.expectedStr.([]string)...); err != nil {
if cx.dialTimeout > 0 && !isGRPCTimedout(err) {
cx.t.Fatalf("roleAddTest #%d: ctlV3Role error (%v)", i, err)
}
}
}
}
}

func roleGrantTest(cx ctlCtx) {
cmdSet := []struct {
args []string
expectedStr string
}{
// Add a role.
{
args: []string{"add", "root"},
expectedStr: "Role root created",
},
// Grant read permission to the role.
{
args: []string{"grant", "root", "read", "foo"},
expectedStr: "Role root updated",
},
// Grant write permission to the role.
{
args: []string{"grant", "root", "write", "foo"},
expectedStr: "Role root updated",
},
// Grant rw permission to the role.
{
args: []string{"grant", "root", "readwrite", "foo"},
expectedStr: "Role root updated",
},
// Try granting invalid permission to the role.
{
args: []string{"grant", "root", "123", "foo"},
expectedStr: "invalid permission type",
},
}

for i, cmd := range cmdSet {
if err := ctlV3Role(cx, cmd.args, cmd.expectedStr); err != nil {
cx.t.Fatalf("roleGrantTest #%d: ctlV3Role error (%v)", i, err)
}
}
}

func ctlV3RoleMultiExpect(cx ctlCtx, args []string, expStr ...string) error {
cmdArgs := append(cx.PrefixArgs(), "role")
cmdArgs = append(cmdArgs, args...)

return e2e.SpawnWithExpects(cmdArgs, cx.envMap, expStr...)
}
func ctlV3Role(cx ctlCtx, args []string, expStr string) error {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 52 and below are retained only in this PR. After auth_test are migrated to common, they should be cleaned up.

cmdArgs := append(cx.PrefixArgs(), "role")
cmdArgs = append(cmdArgs, args...)
Expand Down
51 changes: 51 additions & 0 deletions tests/framework/e2e/etcdctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"strconv"
"strings"

"go.etcd.io/etcd/api/v3/authpb"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/tests/v3/framework/config"
)
Expand Down Expand Up @@ -507,3 +508,53 @@ func (ctl *EtcdctlV3) UserChangePass(user, newPass string) error {
_, err = cmd.Expect("Password updated")
return err
}

func (ctl *EtcdctlV3) RoleAdd(name string) (*clientv3.AuthRoleAddResponse, error) {
var resp clientv3.AuthRoleAddResponse
err := ctl.spawnJsonCmd(&resp, "role", "add", name)
return &resp, err
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost all methods have similar implementation. I think it makes more sense to wrap them into 1 ~ 2 common methods so as to simplify the overall implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good idea, however let's try not to mix in to many changes into PR.

func (ctl *EtcdctlV3) RoleGrantPermission(name string, key, rangeEnd string, permType clientv3.PermissionType) (*clientv3.AuthRoleGrantPermissionResponse, error) {
permissionType := authpb.Permission_Type_name[int32(permType)]
var resp clientv3.AuthRoleGrantPermissionResponse
err := ctl.spawnJsonCmd(&resp, "role", "grant-permission", name, permissionType, key, rangeEnd)
return &resp, err
}

func (ctl *EtcdctlV3) RoleGet(role string) (*clientv3.AuthRoleGetResponse, error) {
var resp clientv3.AuthRoleGetResponse
err := ctl.spawnJsonCmd(&resp, "role", "get", role)
return &resp, err
}

func (ctl *EtcdctlV3) RoleList() (*clientv3.AuthRoleListResponse, error) {
var resp clientv3.AuthRoleListResponse
err := ctl.spawnJsonCmd(&resp, "role", "list")
return &resp, err
}

func (ctl *EtcdctlV3) RoleRevokePermission(role string, key, rangeEnd string) (*clientv3.AuthRoleRevokePermissionResponse, error) {
var resp clientv3.AuthRoleRevokePermissionResponse
err := ctl.spawnJsonCmd(&resp, "role", "revoke-permission", role, key, rangeEnd)
return &resp, err
}

func (ctl *EtcdctlV3) RoleDelete(role string) (*clientv3.AuthRoleDeleteResponse, error) {
var resp clientv3.AuthRoleDeleteResponse
err := ctl.spawnJsonCmd(&resp, "role", "delete", role)
return &resp, err
}

func (ctl *EtcdctlV3) spawnJsonCmd(output interface{}, args ...string) error {
args = append(args, "-w", "json")
cmd, err := SpawnCmd(append(ctl.cmdArgs(), args...), nil)
if err != nil {
return err
}
line, err := cmd.Expect("header")
if err != nil {
return err
}
return json.Unmarshal([]byte(line), output)
}
24 changes: 24 additions & 0 deletions tests/framework/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,27 @@ func (c integrationClient) UserChangePass(user, newPass string) error {
_, err := c.Client.UserChangePassword(context.Background(), user, newPass)
return err
}

func (c integrationClient) RoleAdd(name string) (*clientv3.AuthRoleAddResponse, error) {
return c.Client.RoleAdd(context.Background(), name)
}

func (c integrationClient) RoleGrantPermission(name string, key, rangeEnd string, permType clientv3.PermissionType) (*clientv3.AuthRoleGrantPermissionResponse, error) {
return c.Client.RoleGrantPermission(context.Background(), name, key, rangeEnd, permType)
}

func (c integrationClient) RoleGet(role string) (*clientv3.AuthRoleGetResponse, error) {
return c.Client.RoleGet(context.Background(), role)
}

func (c integrationClient) RoleList() (*clientv3.AuthRoleListResponse, error) {
return c.Client.RoleList(context.Background())
}

func (c integrationClient) RoleRevokePermission(role string, key, rangeEnd string) (*clientv3.AuthRoleRevokePermissionResponse, error) {
return c.Client.RoleRevokePermission(context.Background(), role, key, rangeEnd)
}

func (c integrationClient) RoleDelete(role string) (*clientv3.AuthRoleDeleteResponse, error) {
return c.Client.RoleDelete(context.Background(), role)
}
6 changes: 6 additions & 0 deletions tests/framework/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@ type Client interface {
UserList() (*clientv3.AuthUserListResponse, error)
UserDelete(name string) (*clientv3.AuthUserDeleteResponse, error)
UserChangePass(user, newPass string) error
RoleAdd(name string) (*clientv3.AuthRoleAddResponse, error)
RoleGrantPermission(name string, key, rangeEnd string, permType clientv3.PermissionType) (*clientv3.AuthRoleGrantPermissionResponse, error)
RoleGet(role string) (*clientv3.AuthRoleGetResponse, error)
RoleList() (*clientv3.AuthRoleListResponse, error)
RoleRevokePermission(role string, key, rangeEnd string) (*clientv3.AuthRoleRevokePermissionResponse, error)
RoleDelete(role string) (*clientv3.AuthRoleDeleteResponse, error)
}
Loading