Skip to content

Commit

Permalink
Go: add ReplaceConnectionPassword function in ServerManagementCluster…
Browse files Browse the repository at this point in the history
…Commands

Signed-off-by: umit <umit.unal.89@gmail.com>
Signed-off-by: avifenesh <aviarchi1994@gmail.com>
  • Loading branch information
umit authored and avifenesh committed Nov 12, 2024
1 parent 99c2f7c commit 832c3cb
Show file tree
Hide file tree
Showing 7 changed files with 389 additions and 81 deletions.
74 changes: 74 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type BaseClient interface {
HashCommands
ListCommands
ConnectionManagementCommands
ServerManagementClusterCommands
// Close terminates the client by closing all associated resources.
Close()
}
Expand Down Expand Up @@ -130,6 +131,32 @@ func (client *baseClient) executeCommand(requestType C.RequestType, args []strin
return payload.value, nil
}

func (client *baseClient) executeCommandRequest(message proto.Message) (*C.struct_CommandResponse, error) {
msg, err := proto.Marshal(message)
if err != nil {
return nil, err
}

resultChannel := make(chan payload)
resultChannelPtr := uintptr(unsafe.Pointer(&resultChannel))

requestBytes := C.CBytes(msg)
requestLen := len(msg)

C.send_command_request(
client.coreClient,
C.uintptr_t(resultChannelPtr),
(*C.uchar)(requestBytes),
C.uintptr_t(requestLen),
)
payload := <-resultChannel
if payload.error != nil {
return nil, payload.error
}

return payload.value, nil
}

// Zero copying conversion from go's []string into C pointers
func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) {
cStrings := make([]C.uintptr_t, len(args))
Expand Down Expand Up @@ -540,3 +567,50 @@ func (client *baseClient) PingWithMessage(message string) (string, error) {
}
return response.Value(), nil
}

func (client *baseClient) ConfigGet(args []string) (map[Result[string]]Result[string], error) {
res, err := client.executeCommand(C.ConfigGet, args)
if err != nil {
return nil, err
}

return handleStringToStringMapResponse(res)
}

func (client *baseClient) ConfigSet(parameters map[string]string) (Result[string], error) {
result, err := client.executeCommand(C.ConfigSet, utils.MapToString(parameters))
if err != nil {
return CreateNilStringResult(), err
}

return handleStringResponse(result)
}

func (client *baseClient) CustomCommand(args []string) (interface{}, error) {
res, err := client.executeCommand(C.CustomCommand, args)
if err != nil {
return nil, err
}

resString, err := handleStringOrNullResponse(res)
if err != nil {
return nil, err
}

return resString.Value(), err
}

func (client *baseClient) ReplaceConnectionPassword(password string, reAuth bool) (Result[string], error) {
request := protobuf.CommandRequest{
Command: &protobuf.CommandRequest_ReplaceConnectionPassword{
ReplaceConnectionPassword: &protobuf.ReplaceConnectionPassword{Password: password, ReAuth: reAuth},
},
}

res, err := client.executeCommandRequest(&request)
if err != nil {
return CreateNilStringResult(), err
}

return handleStringResponse(res)
}
92 changes: 92 additions & 0 deletions go/api/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -731,4 +731,96 @@ type ConnectionManagementCommands interface {
//
// [valkey.io]: https://valkey.io/commands/ping/
PingWithMessage(message string) (string, error)

// ReplaceConnectionPassword updates the connection password and optionally re-authenticates.
//
// See [valkey.io] for details.
//
// Parameters:
// password - The new password to set.
// reAuth - If true, re-authenticates the connection with the new password.
//
// Return value:
// A Result containing the operation status, or an error if the password replacement fails.
//
// For example:
// result, err := client.ReplaceConnectionPassword("newpass123", true)
// // result equals api.CreateStringResult("OK")
//
// [valkey.io]: https://valkey.io/commands/auth/
ReplaceConnectionPassword(password string, reAuth bool) (Result[string], error)
}

// ServerManagementClusterCommands provides cluster management operations.
//
// See [valkey.io] for details.
//
// [valkey.io]: https://valkey.io/commands/?group=server
type ServerManagementClusterCommands interface {
// ConfigGet returns values for the specified configuration parameters.
//
// See [valkey.io] for details.
//
// Parameters:
// args - A slice of configuration parameter names to retrieve values for.
//
// Return value:
// A map of parameter names to their values, both wrapped in Result types, or an error if the operation fails.
//
// For example:
// params, err := client.ConfigGet([]string{"timeout", "maxmemory"})
// // timeout equals api.CreateStringResult("1000")
// // maxmemory equals api.CreateStringResult("1GB")
// // params equals map[api.Result[string]]api.Result[string]{timeout: timeout, maxmemory: maxmemory}
//
// [valkey.io]: https://valkey.io/commands/config-get/
ConfigGet(args []string) (map[Result[string]]Result[string], error)

// ConfigSet updates the specified configuration parameters with new values.
//
// See [valkey.io] for details.
//
// Parameters:
// parameters - A map where keys are configuration parameter names and values are their new settings.
//
// Returns:
// - Result[string]: A Result containing "OK" if all parameters were set successfully
// - error: An error if the operation fails for any parameter
//
// Example:
// result, err := client.ConfigSet(map[string]string{
// "timeout": "1000",
// "maxmemory": "1GB",
// })
// // If successful:
// // result.Value() equals "OK"
// // err equals nil
//
// Common configurations:
// - "timeout": Connection timeout in milliseconds
// - "maxmemory": Maximum memory limit (e.g., "1GB", "512MB")
// - "maxclients": Maximum number of client connections
// - "requirepass": Authentication password
//
// [valkey.io]: https://valkey.io/commands/config-set/
ConfigSet(parameters map[string]string) (Result[string], error)

// CustomCommand executes a custom server command with the provided arguments.
//
// See [valkey.io] for details.
//
// Parameters:
// args - A slice containing the command name and its arguments.
//
// Return value:
// An interface{} that must be type asserted to the expected return type, or an error if the command execution fails.
//
// For example:
// result, err := client.CustomCommand([]string{"MYCOMMAND", "arg1", "arg2"})
// // Assuming command returns a string
// // strResult := result.(string)
// // strResult equals "command response"
//
// [valkey.io]: https://valkey.io/commands/
CustomCommand(args []string) (interface{}, error)
}
79 changes: 0 additions & 79 deletions go/api/glide_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package api
// #cgo LDFLAGS: -L../target/release -lglide_rs
// #include "../lib.h"
import "C"
import "github.com/valkey-io/valkey-glide/go/glide/utils"

// GlideClient is a client used for connection in Standalone mode.
type GlideClient struct {
Expand All @@ -21,81 +20,3 @@ func NewGlideClient(config *GlideClientConfiguration) (*GlideClient, error) {

return &GlideClient{client}, nil
}

// CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, including
// the command name and subcommands, should be added as a separate value in args. The returning value depends on the executed
// command.
//
// This function should only be used for single-response commands. Commands that don't return complete response and awaits
// (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's
// behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function.
//
// For example, to return a list of all pub/sub clients:
//
// client.CustomCommand([]string{"CLIENT", "LIST","TYPE", "PUBSUB"})
//
// TODO: Add support for complex return types.
func (client *GlideClient) CustomCommand(args []string) (interface{}, error) {
res, err := client.executeCommand(C.CustomCommand, args)
if err != nil {
return nil, err
}
resString, err := handleStringOrNullResponse(res)
if err != nil {
return nil, err
}
return resString.Value(), err
}

// Sets configuration parameters to the specified values.
//
// Note: Prior to Version 7.0.0, only one parameter can be send.
//
// Parameters:
//
// parameters - A map consisting of configuration parameters and their respective values to set.
//
// Return value:
//
// A api.Result[string] containing "OK" if all configurations have been successfully set. Otherwise, raises an error.
//
// For example:
//
// result, err := client.ConfigSet(map[string]string{"timeout": "1000", "maxmemory": "1GB"})
// result.Value(): "OK"
//
// [valkey.io]: https://valkey.io/commands/config-set/
func (client *GlideClient) ConfigSet(parameters map[string]string) (Result[string], error) {
result, err := client.executeCommand(C.ConfigSet, utils.MapToString(parameters))
if err != nil {
return CreateNilStringResult(), err
}
return handleStringResponse(result)
}

// Gets the values of configuration parameters.
//
// Note: Prior to Version 7.0.0, only one parameter can be send.
//
// Parameters:
//
// args - A slice of configuration parameter names to retrieve values for.
//
// Return value:
//
// A map of api.Result[string] corresponding to the configuration parameters.
//
// For example:
//
// result, err := client.ConfigGet([]string{"timeout" , "maxmemory"})
// result[api.CreateStringResult("timeout")] = api.CreateStringResult("1000")
// result[api.CreateStringResult"maxmemory")] = api.CreateStringResult("1GB")
//
// [valkey.io]: https://valkey.io/commands/config-get/
func (client *GlideClient) ConfigGet(args []string) (map[Result[string]]Result[string], error) {
res, err := client.executeCommand(C.ConfigGet, args)
if err != nil {
return nil, err
}
return handleStringToStringMapResponse(res)
}
30 changes: 30 additions & 0 deletions go/integTest/glide_test_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ func (suite *GlideTestSuite) runWithClients(clients []api.BaseClient, test func(
}
}

func (suite *GlideTestSuite) runWithDefaultClient(test func(client *api.GlideClient)) {
suite.T().Run("Testing with default client", func(t *testing.T) {
test(suite.defaultClient())
})
}

func (suite *GlideTestSuite) runWithClusterClient(test func(client api.BaseClient)) {
suite.T().Run("Testing with cluster client", func(t *testing.T) {
test(suite.defaultClusterClient())
})
}

func (suite *GlideTestSuite) verifyOK(result api.Result[string], err error) {
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), api.OK, result.Value())
Expand All @@ -196,3 +208,21 @@ func (suite *GlideTestSuite) SkipIfServerVersionLowerThanBy(version string) {
suite.T().Skipf("This feature is added in version %s", version)
}
}

func (suite *GlideTestSuite) addAuthConfig(client api.BaseClient) {
config, err := client.ConfigSet(map[string]string{"requirepass": "pass"})
suite.verifyOK(config, err)

auth, err := client.CustomCommand([]string{"AUTH", "pass"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), auth.(string), api.OK)
}

func (suite *GlideTestSuite) removeAuthConfig(client api.BaseClient) {
config, err := client.ConfigSet(map[string]string{"requirepass": ""})
suite.verifyOK(config, err)

reset, err := client.CustomCommand([]string{"RESET"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), reset.(string), "RESET")
}
49 changes: 49 additions & 0 deletions go/integTest/shared_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package integTest

import (
"math"
"strings"
"time"

"github.com/google/uuid"
Expand Down Expand Up @@ -1248,3 +1249,51 @@ func (suite *GlideTestSuite) TestRPush() {
assert.IsType(suite.T(), &api.RequestError{}, err)
})
}

func (suite *GlideTestSuite) TestReplaceConnectionPassword_With_Cluster_Client() {
suite.runWithClusterClient(func(client api.BaseClient) {
suite.addAuthConfig(client)

res, err := client.ReplaceConnectionPassword("newpass", false)
suite.verifyOK(res, err)

key := uuid.NewString()
value := uuid.NewString()

set, err := client.Set(key, value)
suite.verifyOK(set, err)

get, err := client.Get(key)
suite.verifyOK(set, err)
assert.Equal(suite.T(), get.Value(), value)

suite.removeAuthConfig(client)
})
}

func (suite *GlideTestSuite) TestReplaceConnectionPassword_No_Server_Auth_With_ClusterClient() {
suite.runWithClusterClient(func(client api.BaseClient) {
suite.addAuthConfig(client)

res, err := client.ReplaceConnectionPassword("newpass", true)

assert.NotNil(suite.T(), err)
assert.IsType(suite.T(), &api.RequestError{}, err)
assert.Empty(suite.T(), res.Value())

suite.removeAuthConfig(client)
})
}

func (suite *GlideTestSuite) TestReplaceConnectionPassword_Password_Long_With_ClusterClient() {
suite.runWithClusterClient(func(client api.BaseClient) {
suite.addAuthConfig(client)

password := strings.Repeat("p", 1000)

res, err := client.ReplaceConnectionPassword(password, false)
suite.verifyOK(res, err)

suite.removeAuthConfig(client)
})
}
Loading

0 comments on commit 832c3cb

Please sign in to comment.