Commit d7971f96 authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'origin/unstable' into 7.0

parents d2b5a579 acfb4f7a
source "../tests/includes/init-tests.tcl"
# Check if cluster's view of hostnames is consistent
proc are_hostnames_propagated {match_string} {
for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
set cfg [R $j cluster slots]
foreach node $cfg {
for {set i 2} {$i < [llength $node]} {incr i} {
if {! [string match $match_string [lindex [lindex [lindex $node $i] 3] 1]] } {
return 0
}
}
}
}
return 1
}
# Isolate a node from the cluster and give it a new nodeid
proc isolate_node {id} {
set node_id [R $id CLUSTER MYID]
......@@ -212,6 +197,9 @@ test "Verify the nodes configured with prefer hostname only show hostname for ne
test "Test restart will keep hostname information" {
# Set a new hostname, reboot and make sure it sticks
R 0 config set cluster-announce-hostname "restart-1.com"
# Store the hostname in the config
R 0 config rewrite
kill_instance redis 0
restart_instance redis 0
set slot_result [R 0 CLUSTER SLOTS]
assert_equal [lindex [get_slot_field $slot_result 0 2 3] 1] "restart-1.com"
......
source "../tests/includes/init-tests.tcl"
# Initial slot distribution.
set ::slot0 [list 0 1000 1002 5459 5461 5461 10926 10926]
set ::slot1 [list 5460 5460 5462 10922 10925 10925]
set ::slot2 [list 10923 10924 10927 16383]
set ::slot3 [list 1001 1001]
proc cluster_create_with_split_slots {masters replicas} {
for {set j 0} {$j < $masters} {incr j} {
R $j cluster ADDSLOTSRANGE {*}[set ::slot${j}]
}
if {$replicas} {
cluster_allocate_slaves $masters $replicas
}
set ::cluster_master_nodes $masters
set ::cluster_replica_nodes $replicas
}
# Get the node info with the specific node_id from the
# given reference node. Valid type options are "node" and "shard"
proc get_node_info_from_shard {id reference {type node}} {
set shards_response [R $reference CLUSTER SHARDS]
foreach shard_response $shards_response {
set nodes [dict get $shard_response nodes]
foreach node $nodes {
if {[dict get $node id] eq $id} {
if {$type eq "node"} {
return $node
} elseif {$type eq "shard"} {
return $shard_response
} else {
return {}
}
}
}
}
# No shard found, return nothing
return {}
}
test "Create a 8 nodes cluster with 4 shards" {
cluster_create_with_split_slots 4 4
}
test "Cluster should start ok" {
assert_cluster_state ok
}
test "Set cluster hostnames and verify they are propagated" {
for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
R $j config set cluster-announce-hostname "host-$j.com"
}
# Wait for everyone to agree about the state
wait_for_cluster_propagation
}
test "Verify information about the shards" {
set ids {}
for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
lappend ids [R $j CLUSTER MYID]
}
set slots [list $::slot0 $::slot1 $::slot2 $::slot3 $::slot0 $::slot1 $::slot2 $::slot3]
# Verify on each node (primary/replica), the response of the `CLUSTER SLOTS` command is consistent.
for {set ref 0} {$ref < $::cluster_master_nodes + $::cluster_replica_nodes} {incr ref} {
for {set i 0} {$i < $::cluster_master_nodes + $::cluster_replica_nodes} {incr i} {
assert_equal [lindex $slots $i] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "shard"] slots]
assert_equal "host-$i.com" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] hostname]
assert_equal "127.0.0.1" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] ip]
# Default value of 'cluster-preferred-endpoint-type' is ip.
assert_equal "127.0.0.1" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] endpoint]
if {$::tls} {
assert_equal [get_instance_attrib redis $i plaintext-port] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] port]
assert_equal [get_instance_attrib redis $i port] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] tls-port]
} else {
assert_equal [get_instance_attrib redis $i port] [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] port]
}
if {$i < 4} {
assert_equal "master" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] role]
assert_equal "online" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] health]
} else {
assert_equal "replica" [dict get [get_node_info_from_shard [lindex $ids $i] $ref "node"] role]
# Replica could be in online or loading
}
}
}
}
test "Verify no slot shard" {
# Node 8 has no slots assigned
set node_8_id [R 8 CLUSTER MYID]
assert_equal {} [dict get [get_node_info_from_shard $node_8_id 8 "shard"] slots]
assert_equal {} [dict get [get_node_info_from_shard $node_8_id 0 "shard"] slots]
}
set node_0_id [R 0 CLUSTER MYID]
test "Kill a node and tell the replica to immediately takeover" {
kill_instance redis 0
R 4 cluster failover force
}
# Primary 0 node should report as fail, wait until the new primary acknowledges it.
test "Verify health as fail for killed node" {
wait_for_condition 50 100 {
"fail" eq [dict get [get_node_info_from_shard $node_0_id 4 "node"] "health"]
} else {
fail "New primary never detected the node failed"
}
}
set primary_id 4
set replica_id 0
test "Restarting primary node" {
restart_instance redis $replica_id
}
test "Instance #0 gets converted into a replica" {
wait_for_condition 1000 50 {
[RI $replica_id role] eq {slave}
} else {
fail "Old primary was not converted into replica"
}
}
test "Test the replica reports a loading state while it's loading" {
# Test the command is good for verifying everything moves to a happy state
set replica_cluster_id [R $replica_id CLUSTER MYID]
wait_for_condition 50 1000 {
[dict get [get_node_info_from_shard $replica_cluster_id $primary_id "node"] health] eq "online"
} else {
fail "Replica never transitioned to online"
}
# Set 1 MB of data, so there is something to load on full sync
R $primary_id debug populate 1000 key 1000
# Kill replica client for primary and load new data to the primary
R $primary_id config set repl-backlog-size 100
# Set the key load delay so that it will take at least
# 2 seconds to fully load the data.
R $replica_id config set key-load-delay 4000
# Trigger event loop processing every 1024 bytes, this trigger
# allows us to send and receive cluster messages, so we are setting
# it low so that the cluster messages are sent more frequently.
R $replica_id config set loading-process-events-interval-bytes 1024
R $primary_id multi
R $primary_id client kill type replica
# populate the correct data
set num 100
set value [string repeat A 1024]
for {set j 0} {$j < $num} {incr j} {
# Use hashtag valid for shard #0
set key "{ch3}$j"
R $primary_id set $key $value
}
R $primary_id exec
# The replica should reconnect and start a full sync, it will gossip about it's health to the primary.
wait_for_condition 50 1000 {
"loading" eq [dict get [get_node_info_from_shard $replica_cluster_id $primary_id "node"] health]
} else {
fail "Replica never transitioned to loading"
}
# Speed up the key loading and verify everything resumes
R $replica_id config set key-load-delay 0
wait_for_condition 50 1000 {
"online" eq [dict get [get_node_info_from_shard $replica_cluster_id $primary_id "node"] health]
} else {
fail "Replica never transitioned to online"
}
# Final sanity, the replica agrees it is online.
assert_equal "online" [dict get [get_node_info_from_shard $replica_cluster_id $replica_id "node"] health]
}
\ No newline at end of file
......@@ -228,6 +228,30 @@ start_server {tags {"cli"}} {
file delete $tmpfile
}
test_tty_cli "Escape character in JSON mode" {
# reverse solidus
r hset solidus \/ \/
assert_equal \/ \/ [run_cli hgetall solidus]
set escaped_reverse_solidus \"\\"
assert_equal $escaped_reverse_solidus $escaped_reverse_solidus [run_cli --json hgetall \/]
# non printable (0xF0 in ISO-8859-1, not UTF-8(0xC3 0xB0))
set eth "\u00f0\u0065"
r hset eth test $eth
assert_equal \"\\xf0e\" [run_cli hget eth test]
assert_equal \"\u00f0e\" [run_cli --json hget eth test]
assert_equal \"\\\\xf0e\" [run_cli --quoted-json hget eth test]
# control characters
r hset control test "Hello\x00\x01\x02\x03World"
assert_equal \"Hello\\u0000\\u0001\\u0002\\u0003World" [run_cli --json hget control test]
# non-string keys
r hset numkey 1 One
assert_equal \{\"1\":\"One\"\} [run_cli --json hgetall numkey]
# non-string, non-printable keys
r hset npkey "K\u0000\u0001ey" "V\u0000\u0001alue"
assert_equal \{\"K\\u0000\\u0001ey\":\"V\\u0000\\u0001alue\"\} [run_cli --json hgetall npkey]
assert_equal \{\"K\\\\x00\\\\x01ey\":\"V\\\\x00\\\\x01alue\"\} [run_cli --quoted-json hgetall npkey]
}
test_nontty_cli "Status reply" {
assert_equal "OK" [run_cli set key bar]
assert_equal "bar" [r get key]
......@@ -322,7 +346,7 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
set dir [lindex [r config get dir] 1]
assert_equal "OK" [r debug populate 100000 key 1000]
assert_equal "OK" [r function load lua lib1 "redis.register_function('func1', function() return 123 end)"]
assert_equal "lib1" [r function load "#!lua name=lib1\nredis.register_function('func1', function() return 123 end)"]
if {$functions_only} {
set args "--functions-rdb $dir/cli.rdb"
} else {
......@@ -335,10 +359,10 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS
file rename "$dir/cli.rdb" "$dir/dump.rdb"
assert_equal "OK" [r set should-not-exist 1]
assert_equal "OK" [r function load lua should_not_exist_func "redis.register_function('should_not_exist_func', function() return 456 end)"]
assert_equal "should_not_exist_func" [r function load "#!lua name=should_not_exist_func\nredis.register_function('should_not_exist_func', function() return 456 end)"]
assert_equal "OK" [r debug reload nosave]
assert_equal {} [r get should-not-exist]
assert_equal {{library_name lib1 engine LUA description {} functions {{name func1 description {} flags {}}}}} [r function list]
assert_equal {{library_name lib1 engine LUA functions {{name func1 description {} flags {}}}}} [r function list]
if {$functions_only} {
assert_equal 0 [r dbsize]
} else {
......
......@@ -47,7 +47,7 @@ start_server {tags {"repl external:skip"}} {
set slave [srv 0 client]
# Load some functions to be used later
$master FUNCTION load lua test replace {
$master FUNCTION load replace {#!lua name=test
redis.register_function{function_name='f_default_flags', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={}}
redis.register_function{function_name='f_no_writes', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={'no-writes'}}
}
......
......@@ -523,10 +523,14 @@ foreach testType {Successful Aborted} {
$replica set mykey myvalue
# Set a function value on replica to check status during loading, on failure and after swapping db
$replica function load LUA test {redis.register_function('test', function() return 'hello1' end)}
$replica function load {#!lua name=test
redis.register_function('test', function() return 'hello1' end)
}
# Set a function value on master to check it reaches the replica when replication ends
$master function load LUA test {redis.register_function('test', function() return 'hello2' end)}
$master function load {#!lua name=test
redis.register_function('test', function() return 'hello2' end)
}
# Force the replica to try another full sync (this time it will have matching master replid)
$master multi
......@@ -659,7 +663,9 @@ test {diskless loading short read} {
set start [clock clicks -milliseconds]
# Set a function value to check short read handling on functions
r function load LUA test {redis.register_function('test', function() return 'hello1' end)}
r function load {#!lua name=test
redis.register_function('test', function() return 'hello1' end)
}
for {set k 0} {$k < 3} {incr k} {
for {set i 0} {$i < 10} {incr i} {
......
......@@ -54,7 +54,9 @@ TEST_MODULES = \
subcommands.so \
reply.so \
cmdintrospection.so \
eventloop.so
eventloop.so \
moduleconfigs.so \
moduleconfigstwo.so
.PHONY: all
......
......@@ -136,6 +136,22 @@ int rm_call_aclcheck_cmd_module_user(RedisModuleCtx *ctx, RedisModuleString **ar
return res;
}
int rm_call_aclcheck_with_errors(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if(argc < 2){
return RedisModule_WrongArity(ctx);
}
const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "vEC", argv + 2, argc - 2);
RedisModule_ReplyWithCallReply(ctx, rep);
RedisModule_FreeCallReply(rep);
return REDISMODULE_OK;
}
/* A wrap for RM_Call that pass the 'C' flag to do ACL check on the command. */
int rm_call_aclcheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
REDISMODULE_NOT_USED(argv);
......@@ -190,5 +206,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"write",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call_with_errors", rm_call_aclcheck_with_errors,
"write",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
......@@ -54,6 +54,16 @@ int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
}
/* This command redacts every other arguments and returns OK */
int Auth_RedactedAPI(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
for(int i = argc - 1; i > 0; i -= 2) {
int result = RedisModule_RedactClientCommandArgument(ctx, i);
RedisModule_Assert(result == REDISMODULE_OK);
}
return RedisModule_ReplyWithSimpleString(ctx, "OK");
}
int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
......@@ -87,6 +97,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"auth.redact",
Auth_RedactedAPI,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
......
......@@ -718,6 +718,25 @@ end:
/* Return 1 if the reply matches the specified string, otherwise log errors
* in the server log and return 0. */
int TestAssertErrorReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
RedisModuleString *mystr, *expected;
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ERROR) {
return 0;
}
mystr = RedisModule_CreateStringFromCallReply(reply);
expected = RedisModule_CreateString(ctx,str,len);
if (RedisModule_StringCompare(mystr,expected) != 0) {
const char *mystr_ptr = RedisModule_StringPtrLen(mystr,NULL);
const char *expected_ptr = RedisModule_StringPtrLen(expected,NULL);
RedisModule_Log(ctx,"warning",
"Unexpected Error reply reply '%s' (instead of '%s')",
mystr_ptr, expected_ptr);
return 0;
}
return 1;
}
int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
RedisModuleString *mystr, *expected;
......@@ -846,6 +865,18 @@ int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 0),"test",4)) goto fail;
if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 1),"1234",4)) goto fail;
T("foo", "E");
if (!TestAssertErrorReply(ctx,reply,"ERR Unknown Redis command 'foo'.",32)) goto fail;
T("set", "Ec", "x");
if (!TestAssertErrorReply(ctx,reply,"ERR Wrong number of args calling Redis command 'set'.",53)) goto fail;
T("shutdown", "SE");
if (!TestAssertErrorReply(ctx,reply,"ERR command 'shutdown' is not allowed on script mode",52)) goto fail;
T("set", "WEcc", "x", "1");
if (!TestAssertErrorReply(ctx,reply,"ERR Write command 'set' was called while write is not allowed.",62)) goto fail;
RedisModule_ReplyWithSimpleString(ctx,"ALL TESTS PASSED");
return REDISMODULE_OK;
......
......@@ -195,7 +195,7 @@ int do_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "v", argv + 2, argc - 2);
RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "Ev", argv + 2, argc - 2);
if(!rep){
RedisModule_ReplyWithError(ctx, "NULL reply returned");
}else{
......
......@@ -135,22 +135,29 @@ int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
}
/* FSL.BPOP <key> <timeout> - Block clients until list has two or more elements.
/* FSL.BPOP <key> <timeout> [NO_TO_CB]- Block clients until list has two or more elements.
* When that happens, unblock client and pop the last two elements (from the right). */
int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 3)
if (argc < 3)
return RedisModule_WrongArity(ctx);
long long timeout;
if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK || timeout < 0)
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
int to_cb = 1;
if (argc == 4) {
if (strcasecmp("NO_TO_CB", RedisModule_StringPtrLen(argv[3], NULL)))
return RedisModule_ReplyWithError(ctx,"ERR invalid argument");
to_cb = 0;
}
fsl_t *fsl;
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
return REDISMODULE_OK;
if (!fsl) {
RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, bpop_timeout_callback,
RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, to_cb ? bpop_timeout_callback : NULL,
NULL, timeout, &argv[1], 1, NULL);
} else {
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
......
......@@ -11,7 +11,6 @@
* 4- test.oneshot : Test for oneshot API
*/
#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"
#include <stdlib.h>
#include <unistd.h>
......
......@@ -267,6 +267,18 @@ void swapDbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void
LogNumericEvent(ctx, "swapdb-second", ei->dbnum_second);
}
void configChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
{
REDISMODULE_NOT_USED(e);
if (sub != REDISMODULE_SUBEVENT_CONFIG_CHANGE) {
return;
}
RedisModuleConfigChangeV1 *ei = data;
LogNumericEvent(ctx, "config-change-count", ei->num_changes);
LogStringEvent(ctx, "config-change-first", ei->config_names[0]);
}
/* This function must be present on each Redis module. It is used in order to
* register the commands into the Redis server. */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
......@@ -317,6 +329,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_SubscribeToServerEvent(ctx,
RedisModuleEvent_SwapDB, swapDbCallback);
RedisModule_SubscribeToServerEvent(ctx,
RedisModuleEvent_Config, configChangeCallback);
event_log = RedisModule_CreateDict(ctx);
if (RedisModule_CreateCommand(ctx,"hooks.event_count", cmdEventCount,"",0,0,0) == REDISMODULE_ERR)
......
#include "redismodule.h"
#include <strings.h>
int mutable_bool_val;
int immutable_bool_val;
long long longval;
long long memval;
RedisModuleString *strval = NULL;
int enumval;
/* Series of get and set callbacks for each type of config, these rely on the privdata ptr
* to point to the config, and they register the configs as such. Note that one could also just
* use names if they wanted, and store anything in privdata. */
int getBoolConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
return (*(int *)privdata);
}
int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
*(int *)privdata = new;
return REDISMODULE_OK;
}
long long getNumericConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
return (*(long long *) privdata);
}
int setNumericConfigCommand(const char *name, long long new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
*(long long *)privdata = new;
return REDISMODULE_OK;
}
RedisModuleString *getStringConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(privdata);
return strval;
}
int setStringConfigCommand(const char *name, RedisModuleString *new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
REDISMODULE_NOT_USED(privdata);
size_t len;
if (!strcasecmp(RedisModule_StringPtrLen(new, &len), "rejectisfreed")) {
*err = RedisModule_CreateString(NULL, "Cannot set string to 'rejectisfreed'", 36);
return REDISMODULE_ERR;
}
if (strval) RedisModule_FreeString(NULL, strval);
RedisModule_RetainString(NULL, new);
strval = new;
return REDISMODULE_OK;
}
int getEnumConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(privdata);
return enumval;
}
int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(name);
REDISMODULE_NOT_USED(err);
REDISMODULE_NOT_USED(privdata);
enumval = val;
return REDISMODULE_OK;
}
int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(privdata);
if (mutable_bool_val && immutable_bool_val) {
*err = RedisModule_CreateString(NULL, "Bool configs cannot both be yes.", 32);
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int longlongApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(privdata);
if (longval == memval) {
*err = RedisModule_CreateString(NULL, "These configs cannot equal each other.", 38);
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "moduleconfigs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_RegisterBoolConfig(ctx, "mutable_bool", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &mutable_bool_val) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* Immutable config here. */
if (RedisModule_RegisterBoolConfig(ctx, "immutable_bool", 0, REDISMODULE_CONFIG_IMMUTABLE, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &immutable_bool_val) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_RegisterStringConfig(ctx, "string", "secret password", REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* On the stack to make sure we're copying them. */
const char *enum_vals[3] = {"one", "two", "three"};
const int int_vals[3] = {0, 2, 4};
if (RedisModule_RegisterEnumConfig(ctx, "enum", 0, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
/* Memory config here. */
if (RedisModule_RegisterNumericConfig(ctx, "memory_numeric", 1024, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_MEMORY, 0, 3000000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &memval) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_RegisterNumericConfig(ctx, "numeric", -1, REDISMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
size_t len;
if (argc && !strcasecmp(RedisModule_StringPtrLen(argv[0], &len), "noload")) {
return REDISMODULE_OK;
} else if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
if (strval) {
RedisModule_FreeString(ctx, strval);
strval = NULL;
}
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
REDISMODULE_NOT_USED(ctx);
if (strval) {
RedisModule_FreeString(ctx, strval);
strval = NULL;
}
return REDISMODULE_OK;
}
\ No newline at end of file
#include "redismodule.h"
#include <strings.h>
/* Second module configs module, for testing.
* Need to make sure that multiple modules with configs don't interfere with each other */
int bool_config;
int getBoolConfigCommand(const char *name, void *privdata) {
REDISMODULE_NOT_USED(privdata);
if (!strcasecmp(name, "test")) {
return bool_config;
}
return 0;
}
int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
REDISMODULE_NOT_USED(privdata);
REDISMODULE_NOT_USED(err);
if (!strcasecmp(name, "test")) {
bool_config = new;
return REDISMODULE_OK;
}
return REDISMODULE_ERR;
}
/* No arguments are expected */
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "configs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (RedisModule_RegisterBoolConfig(ctx, "test", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, NULL, &argc) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
\ No newline at end of file
......@@ -2,6 +2,162 @@
source "../tests/includes/init-tests.tcl"
set num_sentinels [llength $::sentinel_instances]
set ::user "testuser"
set ::password "secret"
proc server_set_password {} {
foreach_redis_id id {
assert_equal {OK} [R $id CONFIG SET requirepass $::password]
assert_equal {OK} [R $id AUTH $::password]
assert_equal {OK} [R $id CONFIG SET masterauth $::password]
}
}
proc server_reset_password {} {
foreach_redis_id id {
assert_equal {OK} [R $id CONFIG SET requirepass ""]
assert_equal {OK} [R $id CONFIG SET masterauth ""]
}
}
proc server_set_acl {id} {
assert_equal {OK} [R $id ACL SETUSER $::user on >$::password allchannels +@all]
assert_equal {OK} [R $id ACL SETUSER default off]
R $id CLIENT KILL USER default SKIPME no
assert_equal {OK} [R $id AUTH $::user $::password]
assert_equal {OK} [R $id CONFIG SET masteruser $::user]
assert_equal {OK} [R $id CONFIG SET masterauth $::password]
}
proc server_reset_acl {id} {
assert_equal {OK} [R $id ACL SETUSER default on]
assert_equal {1} [R $id ACL DELUSER $::user]
assert_equal {OK} [R $id CONFIG SET masteruser ""]
assert_equal {OK} [R $id CONFIG SET masterauth ""]
}
proc verify_sentinel_connect_replicas {id} {
foreach replica [S $id SENTINEL REPLICAS mymaster] {
if {[string match "*disconnected*" [dict get $replica flags]]} {
return 0
}
}
return 1
}
proc wait_for_sentinels_connect_servers { {is_connect 1} } {
foreach_sentinel_id id {
wait_for_condition 1000 50 {
[string match "*disconnected*" [dict get [S $id SENTINEL MASTER mymaster] flags]] != $is_connect
} else {
fail "At least some sentinel can't connect to master"
}
wait_for_condition 1000 50 {
[verify_sentinel_connect_replicas $id] == $is_connect
} else {
fail "At least some sentinel can't connect to replica"
}
}
}
test "Sentinels (re)connection following SENTINEL SET mymaster auth-pass" {
# 3 types of sentinels to test:
# (re)started while master changed pwd. Manage to connect only after setting pwd
set sent2re 0
# (up)dated in advance with master new password
set sent2up 1
# (un)touched. Yet manage to maintain (old) connection
set sent2un 2
wait_for_sentinels_connect_servers
kill_instance sentinel $sent2re
server_set_password
assert_equal {OK} [S $sent2up SENTINEL SET mymaster auth-pass $::password]
restart_instance sentinel $sent2re
# Verify sentinel that restarted failed to connect master
if {![string match "*disconnected*" [dict get [S $sent2re SENTINEL MASTER mymaster] flags]]} {
fail "Expected to be disconnected from master due to wrong password"
}
# Update restarted sentinel with master password
assert_equal {OK} [S $sent2re SENTINEL SET mymaster auth-pass $::password]
# All sentinels expected to connect successfully
wait_for_sentinels_connect_servers
# remove requirepass and verify sentinels manage to connect servers
server_reset_password
wait_for_sentinels_connect_servers
# Sanity check
verify_sentinel_auto_discovery
}
test "Sentinels (re)connection following master ACL change" {
# Three types of sentinels to test during ACL change:
# 1. (re)started Sentinel. Manage to connect only after setting new pwd
# 2. (up)dated Sentinel, get just before ACL change the new password
# 3. (un)touched Sentinel that kept old connection with master and didn't
# set new ACL password won't persist ACL pwd change (unlike legacy auth-pass)
set sent2re 0
set sent2up 1
set sent2un 2
wait_for_sentinels_connect_servers
# kill sentinel 'sent2re' and restart it after ACL change
kill_instance sentinel $sent2re
# Update sentinel 'sent2up' with new user and pwd
assert_equal {OK} [S $sent2up SENTINEL SET mymaster auth-user $::user]
assert_equal {OK} [S $sent2up SENTINEL SET mymaster auth-pass $::password]
foreach_redis_id id {
server_set_acl $id
}
restart_instance sentinel $sent2re
# Verify sentinel that restarted failed to reconnect master
wait_for_condition 100 50 {
[string match "*disconnected*" [dict get [S $sent2re SENTINEL MASTER mymaster] flags]] != 0
} else {
fail "Expected: Sentinel to be disconnected from master due to wrong password"
}
# Verify sentinel with updated password managed to connect (wait for sentinelTimer to reconnect)
wait_for_condition 100 50 {
[string match "*disconnected*" [dict get [S $sent2up SENTINEL MASTER mymaster] flags]] == 0
} else {
fail "Expected: Sentinel to be connected to master"
}
# Verify sentinel untouched gets failed to connect master
if {![string match "*disconnected*" [dict get [S $sent2un SENTINEL MASTER mymaster] flags]]} {
fail "Expected: Sentinel to be disconnected from master due to wrong password"
}
# Now update all sentinels with new password
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL SET mymaster auth-user $::user]
assert_equal {OK} [S $id SENTINEL SET mymaster auth-pass $::password]
}
# All sentinels expected to connect successfully
wait_for_sentinels_connect_servers
# remove requirepass and verify sentinels manage to connect servers
foreach_redis_id id {
server_reset_acl $id
}
wait_for_sentinels_connect_servers
# Sanity check
verify_sentinel_auto_discovery
}
test "Set parameters in normal case" {
set info [S 0 SENTINEL master mymaster]
......
......@@ -24,7 +24,6 @@ proc ensure_master_up {} {
}
}
proc ensure_master_down {} {
S $::alive_sentinel sentinel debug info-period 1000
S $::alive_sentinel sentinel debug ping-period 100
......@@ -45,10 +44,14 @@ test "Crash the majority of Sentinels to prevent failovers for this unit" {
}
test "SDOWN is triggered by non-responding but not crashed instance" {
lassign [S $::alive_sentinel SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port
ensure_master_up
exec ../../../src/redis-cli -h $host -p $port {*}[rediscli_tls_config "../../../tests"] debug sleep 3 > /dev/null &
set master_addr [S $::alive_sentinel SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
set master_id [get_instance_id_by_port redis [lindex $master_addr 1]]
set pid [get_instance_attrib redis $master_id pid]
exec kill -SIGSTOP $pid
ensure_master_down
exec kill -SIGCONT $pid
ensure_master_up
}
......
source "../tests/includes/utils.tcl"
proc set_redis_announce_ip {addr} {
foreach_redis_id id {
R $id config set replica-announce-ip $addr
......
......@@ -31,16 +31,23 @@ test "(post-init) Set up ACL configuration" {
test "SENTINEL CONFIG SET handles on-the-fly credentials reconfiguration" {
# Make sure we're starting with a broken state...
after 5000
catch {S 1 SENTINEL CKQUORUM mymaster} err
assert_match {*NOQUORUM*} $err
wait_for_condition 200 50 {
[catch {S 1 SENTINEL CKQUORUM mymaster}] == 1
} else {
fail "Expected: Sentinel to be disconnected from master due to wrong password"
}
assert_error "*NOQUORUM*" {S 1 SENTINEL CKQUORUM mymaster}
foreach_sentinel_id id {
assert_equal {OK} [S $id SENTINEL CONFIG SET sentinel-user $::user]
assert_equal {OK} [S $id SENTINEL CONFIG SET sentinel-pass $::password]
}
after 5000
wait_for_condition 200 50 {
[catch {S 1 SENTINEL CKQUORUM mymaster}] == 0
} else {
fail "Expected: Sentinel to be connected to master after setting password"
}
assert_match {*OK*} [S 1 SENTINEL CKQUORUM mymaster]
}
......
# Initialization tests -- most units will start including this.
proc restart_killed_instances {} {
foreach type {redis sentinel} {
foreach_${type}_id id {
if {[get_instance_attrib $type $id pid] == -1} {
puts -nonewline "$type/$id "
flush stdout
restart_instance $type $id
}
}
}
}
source "../tests/includes/utils.tcl"
test "(init) Restart killed instances" {
restart_killed_instances
......@@ -59,14 +48,7 @@ test "(init) Sentinels can talk with the master" {
}
test "(init) Sentinels are able to auto-discover other sentinels" {
set sentinels [llength $::sentinel_instances]
foreach_sentinel_id id {
wait_for_condition 1000 50 {
[dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
} else {
fail "At least some sentinel can't detect some other sentinel"
}
}
verify_sentinel_auto_discovery
}
test "(init) Sentinels are able to auto-discover slaves" {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment