diff --git a/go/api/connection_management_cluster_commands.go b/go/api/connection_management_cluster_commands.go new file mode 100644 index 0000000000..5790429622 --- /dev/null +++ b/go/api/connection_management_cluster_commands.go @@ -0,0 +1,14 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// Supports commands and transactions for the "Connection Management" group of commands for cluster client. +// +// See [valkey.io] for details. +// +// [valkey.io]: https://valkey.io/commands/#connection +type ConnectionManagementClusterCommands interface { + PingWithRoute(route route) (string, error) + + PingWithMessageRoute(message string, route route) (string, error) +} diff --git a/go/api/generic_cluster_commands.go b/go/api/generic_cluster_commands.go index cd46fca42b..85d9929782 100644 --- a/go/api/generic_cluster_commands.go +++ b/go/api/generic_cluster_commands.go @@ -34,4 +34,6 @@ type GenericClusterCommands interface { // // [Valkey GLIDE Wiki]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command CustomCommand(args []string) (ClusterValue[interface{}], error) + + CustomCommandWithRoute(args []string, route route) (ClusterValue[interface{}], error) } diff --git a/go/api/glide_cluster_client.go b/go/api/glide_cluster_client.go index cc672a91b5..ea677bdcc3 100644 --- a/go/api/glide_cluster_client.go +++ b/go/api/glide_cluster_client.go @@ -13,6 +13,7 @@ var _ GlideClusterClient = (*glideClusterClient)(nil) type GlideClusterClient interface { BaseClient GenericClusterCommands + ConnectionManagementClusterCommands } // glideClusterClient implements cluster mode operations by extending baseClient functionality. @@ -41,3 +42,94 @@ func (client *glideClusterClient) CustomCommand(args []string) (ClusterValue[int } return CreateClusterValue(data), nil } + +// CustomCommandWithRoute 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. +// +// The command will be routed automatically based on the passed command's default request policy. +// +// See [Valkey GLIDE Wiki] for details on the restrictions and limitations of the custom command API. +// +// Parameters: +// +// args - Arguments for the custom command including the command name. +// route - Specifies the routing configuration for the command. The client will route the +// command to the nodes defined by route. +// +// Return value: +// +// The returning value depends on the executed command and route. +// +// For example: +// +// route := api.SimpleNodeRoute(api.RandomRoute) +// result, err := client.CustomCommand([]string{"ping"}, route) +// result.Value().(string): "PONG" +// +// [Valkey GLIDE Wiki]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command +func (client *glideClusterClient) CustomCommandWithRoute(args []string, route route) (ClusterValue[interface{}], error) { + res, err := client.executeCommandWithRoute(C.CustomCommand, args, route) + if err != nil { + return CreateEmptyClusterValue(), err + } + data, err := handleInterfaceResponse(res) + if err != nil { + return CreateEmptyClusterValue(), err + } + return CreateClusterValue(data), nil +} + +// Pings the server. +// +// Parameters: +// +// route - Specifies the routing configuration for the command. +// The client will route the command to the nodes defined by route. +// +// Return value: +// +// Returns "PONG". +// +// For example: +// +// route := api.SimpleNodeRoute(api.RandomRoute) +// result, err := client.Ping(route) +// fmt.Println(result) // Output: "PONG" +// +// [valkey.io]: https://valkey.io/commands/ping/ +func (client *glideClusterClient) PingWithRoute(route route) (string, error) { + res, err := client.executeCommandWithRoute(C.Ping, []string{}, route) + if err != nil { + return defaultStringResponse, err + } + return handleStringResponse(res) +} + +// Pings the server with a custom message. +// +// Parameters: +// +// message - A message to include in the `PING` command. +// route - Specifies the routing configuration for the command. +// The client will route the command to the nodes defined by route. +// +// Return value: +// +// Returns the copy of message. +// +// For example: +// +// route := api.SimpleNodeRoute(api.RandomRoute) +// result, err := client.PingWithMessage("Hello", route) +// fmt.Println(result) // Output: "Hello" +// +// [valkey.io]: https://valkey.io/commands/ping/ +func (client *glideClusterClient) PingWithMessageRoute(message string, route route) (string, error) { + res, err := client.executeCommandWithRoute(C.Ping, []string{message}, route) + if err != nil { + return defaultStringResponse, err + } + return handleStringResponse(res) +} diff --git a/go/integTest/cluster_commands_test.go b/go/integTest/cluster_commands_test.go index 142f0cf273..60726868d3 100644 --- a/go/integTest/cluster_commands_test.go +++ b/go/integTest/cluster_commands_test.go @@ -3,9 +3,11 @@ package integTest import ( + "fmt" "strings" "github.com/stretchr/testify/assert" + "github.com/valkey-io/valkey-glide/go/glide/api" ) func (suite *GlideTestSuite) TestClusterCustomCommandInfo() { @@ -27,3 +29,105 @@ func (suite *GlideTestSuite) TestClusterCustomCommandEcho() { // ECHO is routed to a single random node assert.Equal(suite.T(), "GO GLIDE GO", result.Value().(string)) } + +func (suite *GlideTestSuite) TestPingWithRoute_ValidRoute() { + client := suite.defaultClusterClient() + route := api.SimpleNodeRoute(api.RandomRoute) + result, err := client.PingWithRoute(route) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "PONG", result) +} + +func (suite *GlideTestSuite) TestPingWithRoute_InvalidRoute() { + client := suite.defaultClusterClient() + invalidRoute := api.NewByAddressRoute("invalidHost", 9999) + + result, err := client.PingWithRoute(invalidRoute) + + assert.NotNil(suite.T(), err) + assert.Empty(suite.T(), result) +} + +func (suite *GlideTestSuite) TestPingWithMessageRoute_ValidRoute_ValidMessage() { + client := suite.defaultClusterClient() + route := api.SimpleNodeRoute(api.RandomRoute) + + customMessage := "Hello Glide" + + result, err := client.PingWithMessageRoute(customMessage, route) + + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), customMessage, result) +} + +func (suite *GlideTestSuite) TestPingWithMessageRoute_EmptyMessage() { + client := suite.defaultClusterClient() + route := api.SimpleNodeRoute(api.RandomRoute) + + customMessage := "" + + result, err := client.PingWithMessageRoute(customMessage, route) + + assert.NoError(suite.T(), err) + + assert.Equal(suite.T(), customMessage, result) +} + +func (suite *GlideTestSuite) TestPingWithMessageRoute_InvalidRoute() { + client := suite.defaultClusterClient() + invalidRoute := api.NewByAddressRoute("invalidHost", 9999) + + customMessage := "Hello Glide" + + result, err := client.PingWithMessageRoute(customMessage, invalidRoute) + + assert.NotNil(suite.T(), err) + assert.Empty(suite.T(), result) +} + +func (suite *GlideTestSuite) TestClusterCustomCommandWithRoute_Info() { + client := suite.defaultClusterClient() + route := api.SimpleNodeRoute(api.AllPrimaries) + result, err := client.CustomCommandWithRoute([]string{"INFO"}, route) + assert.Nil(suite.T(), err) + for _, value := range result.Value().(map[string]interface{}) { + assert.True(suite.T(), strings.Contains(value.(string), "# Stats")) + } +} + +func (suite *GlideTestSuite) TestClusterCustomCommandWithRoute_Echo() { + client := suite.defaultClusterClient() + route := api.SimpleNodeRoute(api.RandomRoute) + result, err := client.CustomCommandWithRoute([]string{"ECHO", "GO GLIDE GO"}, route) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "GO GLIDE GO", result.Value().(string)) +} + +func (suite *GlideTestSuite) TestClusterCustomCommandWithRoute_InvalidRoute() { + client := suite.defaultClusterClient() + invalidRoute := api.NewByAddressRoute("invalidHost", 9999) + result, err := client.CustomCommandWithRoute([]string{"PING"}, invalidRoute) + assert.NotNil(suite.T(), err) + assert.Equal(suite.T(), api.CreateEmptyClusterValue(), result) +} + +func (suite *GlideTestSuite) TestClusterCustomCommandWithRoute_AllNodes() { + client := suite.defaultClusterClient() + route := api.SimpleNodeRoute(api.AllNodes) + result, err := client.CustomCommandWithRoute([]string{"PING"}, route) + + assert.Nil(suite.T(), err) + value := result.Value() + + fmt.Printf("Value type: %T\n", value) + + if result.IsMultiValue() { + responses := value.(map[string]interface{}) + assert.Greater(suite.T(), len(responses), 0) + for _, response := range responses { + assert.Equal(suite.T(), "PONG", response.(string)) + } + } else { + assert.Equal(suite.T(), "PONG", value.(string)) + } +}