Skip to content

Commit

Permalink
Add listRoles and dropRole to the client.go (#726)
Browse files Browse the repository at this point in the history
* Add listRoles and dropRole to the client.go

* Add unit tests for create role, list role and drop role, improve validation, fix comments
  • Loading branch information
burmanm authored Nov 12, 2024
1 parent c82c668 commit 4022c5c
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 9 deletions.
82 changes: 81 additions & 1 deletion pkg/httphelper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,17 @@ func (client *NodeMgmtClient) CallSchemaVersionsEndpoint(pod *corev1.Pod) (map[s
return result, nil
}

// Create a new superuser with the given username and password
// CallCreateRoleEndpoint creates a new user with the given username and password
func (client *NodeMgmtClient) CallCreateRoleEndpoint(pod *corev1.Pod, username string, password string, superuser bool) error {
client.Log.Info(
"calling Management API create role - POST /api/v0/ops/auth/role",
"pod", pod.Name,
)

if username == "" || password == "" {
return errors.New("username and password cannot be empty")
}

postData := url.Values{}
postData.Set("username", username)
postData.Set("password", password)
Expand All @@ -324,6 +328,82 @@ func (client *NodeMgmtClient) CallCreateRoleEndpoint(pod *corev1.Pod, username s
return nil
}

// CallDropRoleEndpoint drops an existing role from the cluster
func (client *NodeMgmtClient) CallDropRoleEndpoint(pod *corev1.Pod, username string) error {
client.Log.Info(
"calling Management API drop role - DELETE /api/v0/ops/auth/role",
"pod", pod.Name,
)

if username == "" {
return errors.New("username cannot be empty")
}

postData := url.Values{}
postData.Set("username", username)

podHost, podPort, err := BuildPodHostFromPod(pod)
if err != nil {
return err
}

request := nodeMgmtRequest{
endpoint: fmt.Sprintf("/api/v0/ops/auth/role?%s", postData.Encode()),
host: podHost,
port: podPort,
method: http.MethodDelete,
timeout: 60 * time.Second,
}

_, err = callNodeMgmtEndpoint(client, request, "")
return err
}

type User struct {
Name string `json:"name"`
Super string `json:"super"`
Login string `json:"login"`
Options string `json:"options"`
Datacenters string `json:"datacenters"`
}

func parseListRoles(body []byte) ([]User, error) {
var users []User
if err := json.Unmarshal(body, &users); err != nil {
fmt.Printf("Received an error: %v\n", err)
return nil, err
}
return users, nil
}

// CallListRolesEndpoint lists existing roles in the cluster
func (client *NodeMgmtClient) CallListRolesEndpoint(pod *corev1.Pod) ([]User, error) {
client.Log.Info(
"calling Management API list roles - GET /api/v0/ops/auth/role",
"pod", pod.Name,
)

podHost, podPort, err := BuildPodHostFromPod(pod)
if err != nil {
return nil, err
}

request := nodeMgmtRequest{
endpoint: "/api/v0/ops/auth/role",
host: podHost,
port: podPort,
method: http.MethodGet,
timeout: 60 * time.Second,
}

content, err := callNodeMgmtEndpoint(client, request, "")
if err != nil {
return nil, err
}

return parseListRoles(content)
}

func (client *NodeMgmtClient) CallProbeClusterEndpoint(pod *corev1.Pod, consistencyLevel string, rfPerDc int) error {
client.Log.Info(
"calling Management API cluster health - GET /api/v0/probes/cluster",
Expand Down
84 changes: 76 additions & 8 deletions pkg/httphelper/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/go-logr/logr"
"github.com/k8ssandra/cass-operator/pkg/mocks"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -229,7 +230,7 @@ func TestNodeMgmtClient_GetKeyspaceReplication(t *testing.T) {
"success",
goodPod,
"ks1",
newMockHttpClient(newHttpResponse(successBody, http.StatusOK), nil),
newMockHttpClient(newHttpResponseMarshalled(successBody, http.StatusOK), nil),
successBody,
nil,
},
Expand Down Expand Up @@ -261,7 +262,7 @@ func TestNodeMgmtClient_GetKeyspaceReplication(t *testing.T) {
"keyspace not found",
goodPod,
"ks1",
newMockHttpClient(newHttpResponse("Keyspace 'ks1' does not exist", http.StatusNotFound), nil),
newMockHttpClient(newHttpResponseMarshalled("Keyspace 'ks1' does not exist", http.StatusNotFound), nil),
nil,
&RequestError{
StatusCode: http.StatusNotFound,
Expand Down Expand Up @@ -292,7 +293,7 @@ func TestNodeMgmtClient_ListTables(t *testing.T) {
"success",
goodPod,
"ks1",
newMockHttpClient(newHttpResponse([]string{"table1", "table2"}, http.StatusOK), nil),
newMockHttpClient(newHttpResponseMarshalled([]string{"table1", "table2"}, http.StatusOK), nil),
[]string{"table1", "table2"},
nil,
},
Expand Down Expand Up @@ -324,7 +325,7 @@ func TestNodeMgmtClient_ListTables(t *testing.T) {
"keyspace not found",
goodPod,
"ks1",
newMockHttpClient(newHttpResponse([]string{}, http.StatusOK), nil),
newMockHttpClient(newHttpResponseMarshalled([]string{}, http.StatusOK), nil),
[]string{},
nil,
},
Expand Down Expand Up @@ -361,7 +362,7 @@ func TestNodeMgmtClient_CreateTable(t *testing.T) {
"success",
goodPod,
goodTable,
newMockHttpClient(newHttpResponse("OK", http.StatusOK), nil),
newMockHttpClient(newHttpResponseMarshalled("OK", http.StatusOK), nil),
nil,
},
{
Expand Down Expand Up @@ -423,7 +424,7 @@ func TestNodeMgmtClient_CreateTable(t *testing.T) {
"table1",
NewPartitionKeyColumn("", "int", 0),
),
newMockHttpClient(newHttpResponse("Table creation failed: 'columns[0].name' must not be empty", http.StatusBadRequest), nil),
newMockHttpClient(newHttpResponseMarshalled("Table creation failed: 'columns[0].name' must not be empty", http.StatusBadRequest), nil),
&RequestError{
StatusCode: http.StatusBadRequest,
Err: errors.New("incorrect status code of 400 when calling endpoint"),
Expand All @@ -433,7 +434,7 @@ func TestNodeMgmtClient_CreateTable(t *testing.T) {
"keyspace not found",
goodPod,
goodTable,
newMockHttpClient(newHttpResponse("keyspace does not exist", http.StatusInternalServerError), nil),
newMockHttpClient(newHttpResponseMarshalled("keyspace does not exist", http.StatusInternalServerError), nil),
&RequestError{
StatusCode: http.StatusInternalServerError,
Err: errors.New("incorrect status code of 500 when calling endpoint"),
Expand All @@ -449,6 +450,63 @@ func TestNodeMgmtClient_CreateTable(t *testing.T) {
}
}

func TestListRoles(t *testing.T) {
require := require.New(t)
payload := []byte(`[{"datacenters":"ALL","login":"false","name":"try","options":"{}","super":"false"},{"datacenters":"ALL","login":"true","name":"cluster2-superuser","options":"{}","super":"true"},{"datacenters":"ALL","login":"false","name":"cassandra","options":"{}","super":"false"}]`)
roles, err := parseListRoles(payload)
require.NoError(err)
require.Equal(3, len(roles))

mockHttpClient := mocks.NewHttpClient(t)
mockHttpClient.On("Do",
mock.MatchedBy(
func(req *http.Request) bool {
return req.URL.Path == "/api/v0/ops/auth/role" && req.Method == http.MethodGet
})).
Return(newHttpResponse(payload, http.StatusOK), nil).
Once()

mgmtClient := newMockMgmtClient(mockHttpClient)
roles, err = mgmtClient.CallListRolesEndpoint(goodPod)
require.NoError(err)
require.Equal(3, len(roles))
}

func TestCreateRole(t *testing.T) {
require := require.New(t)
mockHttpClient := mocks.NewHttpClient(t)
mockHttpClient.On("Do",
mock.MatchedBy(
func(req *http.Request) bool {
return req.URL.Path == "/api/v0/ops/auth/role" && req.Method == http.MethodPost && req.URL.Query().Get("username") == "role1" && req.URL.Query().Get("password") == "password1" && req.URL.Query().Get("is_superuser") == "true"
})).
Return(newHttpResponseMarshalled("OK", http.StatusOK), nil).
Once()

mgmtClient := newMockMgmtClient(mockHttpClient)
err := mgmtClient.CallCreateRoleEndpoint(goodPod, "role1", "password1", true)
require.NoError(err)
require.True(mockHttpClient.AssertExpectations(t))
}

func TestDropRole(t *testing.T) {
require := require.New(t)
mockHttpClient := mocks.NewHttpClient(t)
mockHttpClient.On("Do",
mock.MatchedBy(
func(req *http.Request) bool {
return req.URL.Path == "/api/v0/ops/auth/role" && req.Method == http.MethodDelete
})).
Return(newHttpResponseMarshalled("OK", http.StatusOK), nil).
Once()

mgmtClient := newMockMgmtClient(mockHttpClient)
err := mgmtClient.CallDropRoleEndpoint(goodPod, "role1")

require.NoError(err)
require.True(mockHttpClient.AssertExpectations(t))
}

func newMockMgmtClient(httpClient *mocks.HttpClient) *NodeMgmtClient {
return &NodeMgmtClient{
Client: httpClient,
Expand All @@ -463,7 +521,17 @@ func newMockHttpClient(response *http.Response, err error) *mocks.HttpClient {
return httpClient
}

func newHttpResponse(responseBody interface{}, status int) *http.Response {
func newHttpResponse(responseBody []byte, status int) *http.Response {
body := io.NopCloser(bytes.NewReader(responseBody))
bodyLength := int64(len(responseBody))
return &http.Response{
StatusCode: status,
Body: body,
ContentLength: bodyLength,
}
}

func newHttpResponseMarshalled(responseBody interface{}, status int) *http.Response {
marshalled, _ := json.Marshal(responseBody)
body := io.NopCloser(bytes.NewReader(marshalled))
bodyLength := int64(len(marshalled))
Expand Down

0 comments on commit 4022c5c

Please sign in to comment.