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

replica redirect read&write to master in standalone mode #325

Open
wants to merge 4 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/cluster.c
Expand Up @@ -1461,20 +1461,12 @@ void askingCommand(client *c) {
* In this mode slaves will not redirect clients as long as clients access
* with read-only commands to keys that are served by the slave's master. */
void readonlyCommand(client *c) {
if (server.cluster_enabled == 0) {
addReplyError(c,"This instance has cluster support disabled");
return;
}
c->flags |= CLIENT_READONLY;
addReply(c,shared.ok);
}

/* The READWRITE command just clears the READONLY command state. */
void readwriteCommand(client *c) {
if (server.cluster_enabled == 0) {
addReplyError(c,"This instance has cluster support disabled");
return;
}
c->flags &= ~CLIENT_READONLY;
addReply(c,shared.ok);
}
23 changes: 23 additions & 0 deletions src/commands.def
Expand Up @@ -1083,6 +1083,28 @@ struct COMMAND_ARG CLIENT_CACHING_Args[] = {
{MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_CACHING_mode_Subargs},
};

/********** CLIENT CAPA ********************/

#ifndef SKIP_CMD_HISTORY_TABLE
/* CLIENT CAPA history */
#define CLIENT_CAPA_History NULL
#endif

#ifndef SKIP_CMD_TIPS_TABLE
/* CLIENT CAPA tips */
#define CLIENT_CAPA_Tips NULL
#endif

#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* CLIENT CAPA key specs */
#define CLIENT_CAPA_Keyspecs NULL
#endif

/* CLIENT CAPA argument table */
struct COMMAND_ARG CLIENT_CAPA_Args[] = {
{MAKE_ARG("capability",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};

/********** CLIENT GETNAME ********************/

#ifndef SKIP_CMD_HISTORY_TABLE
Expand Down Expand Up @@ -1543,6 +1565,7 @@ struct COMMAND_ARG CLIENT_UNBLOCK_Args[] = {
/* CLIENT command table */
struct COMMAND_STRUCT CLIENT_Subcommands[] = {
{MAKE_CMD("caching","Instructs the server whether to track the keys in the next request.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,0,CLIENT_CACHING_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_CACHING_Keyspecs,0,NULL,1),.args=CLIENT_CACHING_Args},
{MAKE_CMD("capa","A client claims its capability.","O(1)","8.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CAPA_History,0,CLIENT_CAPA_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_CAPA_Keyspecs,0,NULL,1),.args=CLIENT_CAPA_Args},
{MAKE_CMD("getname","Returns the name of the connection.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,0,CLIENT_GETNAME_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETNAME_Keyspecs,0,NULL,0)},
{MAKE_CMD("getredir","Returns the client ID to which the connection's tracking notifications are redirected.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,0,CLIENT_GETREDIR_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETREDIR_Keyspecs,0,NULL,0)},
{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_HELP_Keyspecs,0,NULL,0)},
Expand Down
28 changes: 28 additions & 0 deletions src/commands/client-capa.json
@@ -0,0 +1,28 @@
{
"CAPA": {
"summary": "A client claims its capability.",
"complexity": "O(1)",
"group": "connection",
"since": "8.0.0",
"arity": 3,
"container": "CLIENT",
"function": "clientCommand",
"command_flags": [
"NOSCRIPT",
"LOADING",
"STALE"
],
"acl_categories": [
"CONNECTION"
],
"reply_schema": {
"const": "OK"
},
"arguments": [
{
"name": "capability",
"type": "string"
}
]
}
}
8 changes: 8 additions & 0 deletions src/networking.c
Expand Up @@ -166,6 +166,7 @@ client *createClient(connection *conn) {
c->bulklen = -1;
c->sentlen = 0;
c->flags = 0;
c->capa = 0;
c->slot = -1;
c->ctime = c->lastinteraction = server.unixtime;
c->duration = 0;
Expand Down Expand Up @@ -3583,6 +3584,13 @@ NULL
} else {
addReplyErrorObject(c,shared.syntaxerr);
}
} else if (!strcasecmp(c->argv[1]->ptr,"capa") && c->argc == 3) {
if (!strcasecmp(c->argv[2]->ptr,"redirect")) {
c->capa |= CLIENT_CAPA_REDIRECT;
addReply(c,shared.ok);
} else {
addReplyErrorObject(c,shared.syntaxerr);
}
} else {
addReplySubcommandSyntaxError(c);
}
Expand Down
11 changes: 11 additions & 0 deletions src/server.c
Expand Up @@ -4039,6 +4039,17 @@ int processCommand(client *c) {
}
}

if (!server.cluster_enabled &&
c->capa & CLIENT_CAPA_REDIRECT &&
server.masterhost &&
!mustObeyClient(c) &&
(is_write_command ||
(is_read_command && !(c->flags & CLIENT_READONLY)))) {
addReplyErrorSds(c,sdscatprintf(sdsempty(), "-REDIRECT %s:%d",
Copy link

Choose a reason for hiding this comment

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

Why is -1 missing here? Keep the same format as MOVED and the client can reuse the parsing code.

Copy link
Contributor

Choose a reason for hiding this comment

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

-1 is not part of MOVED.

MOVED is for a slot. REDIRECT is for all writes to the node, without slot. (Only cluster has slots.)

Choose a reason for hiding this comment

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

MOVED is for a slot. REDIRECT is for all writes to the node, without slot. (Only cluster has slots.)

ok, that's reasonable from valkey developer.

server.masterhost, server.masterport));
return C_OK;
}

/* Disconnect some clients if total clients memory is too high. We do this
* before key eviction, after the last command was executed and consumed
* some client output buffer memory. */
Expand Down
4 changes: 4 additions & 0 deletions src/server.h
Expand Up @@ -404,6 +404,9 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define CLIENT_REPROCESSING_COMMAND (1ULL<<50) /* The client is re-processing the command. */
#define CLIENT_PREREPL_DONE (1ULL<<51) /* Indicate that pre-replication has been done on the client */

/* Client capabilities */
#define CLIENT_CAPA_REDIRECT (1<<0) /* Indicate that the client can handle redirection */

/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
typedef enum blocking_type {
Expand Down Expand Up @@ -1164,6 +1167,7 @@ typedef struct {
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
uint64_t flags; /* Client flags: CLIENT_* macros. */
uint64_t capa; /* Client capabilities: CLIENT_CAPA* macros. */
connection *conn;
int resp; /* RESP protocol version. Can be 2 or 3. */
serverDb *db; /* Pointer to currently SELECTed DB. */
Expand Down
36 changes: 36 additions & 0 deletions tests/integration/replica-redirect.tcl
@@ -0,0 +1,36 @@
start_server {tags {needs:repl external:skip}} {
start_server {} {
set master_host [srv -1 host]
set master_port [srv -1 port]

r replicaof $master_host $master_port
wait_for_condition 50 100 {
[s 0 master_link_status] eq {up}
} else {
fail "Replicas not replicating from master"
}

test {replica allow read command by default} {
r get foo
} {}

test {replica reply READONLY error for write command by default} {
assert_error {READONLY*} {r set foo bar}
}

test {replica redirect read and write command after CLIENT CAPA REDIRECT} {
r client capa redirect
assert_error "REDIRECT $master_host:$master_port" {r set foo bar}
assert_error "REDIRECT $master_host:$master_port" {r get foo}
}

test {non-data access commands are not redirected} {
r ping
} {PONG}

test {replica allow read command in READONLY mode} {
r readonly
r get foo
} {}
}
}