| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| #include <gtest/gtest.h> |
| |
| #include <filesystem> |
| #include <fstream> |
| #include <iostream> |
| #include <map> |
| #include <vector> |
| |
| #include "commands/commander.h" |
| #include "config/config_util.h" |
| #include "server/server.h" |
| |
| TEST(Config, GetAndSet) { |
| const char *path = "test.conf"; |
| Config config; |
| |
| auto s = config.Load(CLIOptions(path)); |
| EXPECT_FALSE(s.IsOK()); |
| std::map<std::string, std::string> mutable_cases = { |
| {"workers", "4"}, |
| {"log-level", "info"}, |
| {"timeout", "1000"}, |
| {"maxclients", "2000"}, |
| {"max-backup-to-keep", "1"}, |
| {"max-backup-keep-hours", "4000"}, |
| {"requirepass", "mytest_requirepass"}, |
| {"masterauth", "mytest_masterauth"}, |
| {"compact-cron", "1 2 3 4 5"}, |
| {"bgsave-cron", "5 4 3 2 1"}, |
| {"dbsize-scan-cron", "1 2 3 2 1"}, |
| {"max-io-mb", "5000"}, |
| {"max-db-size", "6000"}, |
| {"max-replication-mb", "7000"}, |
| {"slave-serve-stale-data", "no"}, |
| {"slave-read-only", "no"}, |
| {"slave-priority", "101"}, |
| {"slowlog-log-slower-than", "1234"}, |
| {"slowlog-max-len", "123"}, |
| {"profiling-sample-ratio", "50"}, |
| {"profiling-sample-record-max-len", "1"}, |
| {"profiling-sample-record-threshold-ms", "50"}, |
| {"profiling-sample-commands", "get,set"}, |
| {"backup-dir", "test_dir/backup"}, |
| |
| {"rocksdb.compression", "no"}, |
| {"rocksdb.max_open_files", "1234"}, |
| {"rocksdb.write_buffer_size", "1234"}, |
| {"rocksdb.max_write_buffer_number", "1"}, |
| {"rocksdb.min_write_buffer_number_to_merge", "1"}, |
| {"rocksdb.target_file_size_base", "100"}, |
| {"rocksdb.max_background_compactions", "-1"}, |
| {"rocksdb.max_subcompactions", "3"}, |
| {"rocksdb.delayed_write_rate", "1234"}, |
| {"rocksdb.stats_dump_period_sec", "600"}, |
| {"rocksdb.compaction_readahead_size", "1024"}, |
| {"rocksdb.level0_slowdown_writes_trigger", "50"}, |
| {"rocksdb.level0_stop_writes_trigger", "100"}, |
| {"rocksdb.enable_blob_files", "no"}, |
| {"rocksdb.min_blob_size", "4096"}, |
| {"rocksdb.blob_file_size", "268435456"}, |
| {"rocksdb.enable_blob_garbage_collection", "yes"}, |
| {"rocksdb.blob_garbage_collection_age_cutoff", "25"}, |
| {"rocksdb.max_bytes_for_level_base", "268435456"}, |
| {"rocksdb.max_bytes_for_level_multiplier", "10"}, |
| {"rocksdb.level_compaction_dynamic_level_bytes", "yes"}, |
| {"rocksdb.max_background_jobs", "4"}, |
| {"rocksdb.compression_start_level", "2"}, |
| {"rocksdb.sst_file_delete_rate_bytes_per_sec", "0"}, |
| {"max-replication-lag", "50000000"}, |
| {"replication-send-timeout-ms", "60000"}, |
| }; |
| std::vector<std::string> values; |
| for (const auto &iter : mutable_cases) { |
| s = config.Set(nullptr, iter.first, iter.second); |
| ASSERT_TRUE(s.IsOK()); |
| config.Get(iter.first, &values); |
| ASSERT_TRUE(s.IsOK()); |
| ASSERT_EQ(values.size(), 2); |
| EXPECT_EQ(values[0], iter.first); |
| EXPECT_EQ(values[1], iter.second); |
| } |
| ASSERT_TRUE(config.Rewrite({}).IsOK()); |
| s = config.Load(CLIOptions(path)); |
| EXPECT_TRUE(s.IsOK()); |
| for (const auto &iter : mutable_cases) { |
| s = config.Set(nullptr, iter.first, iter.second); |
| ASSERT_TRUE(s.IsOK()); |
| config.Get(iter.first, &values); |
| ASSERT_EQ(values.size(), 2); |
| EXPECT_EQ(values[0], iter.first); |
| EXPECT_EQ(values[1], iter.second); |
| } |
| unlink(path); |
| |
| std::map<std::string, std::string> immutable_cases = { |
| {"daemonize", "yes"}, |
| {"bind", "0.0.0.0"}, |
| {"repl-bind", "0.0.0.0"}, |
| {"repl-workers", "8"}, |
| {"tcp-backlog", "500"}, |
| {"slaveof", "no one"}, |
| {"db-name", "test_dbname"}, |
| {"dir", "test_dir"}, |
| {"pidfile", "test.pid"}, |
| {"supervised", "no"}, |
| {"rocksdb.block_size", "1234"}, |
| {"rocksdb.max_background_flushes", "-1"}, |
| {"rocksdb.wal_ttl_seconds", "10000"}, |
| {"rocksdb.wal_size_limit_mb", "16"}, |
| {"rocksdb.enable_pipelined_write", "no"}, |
| {"rocksdb.cache_index_and_filter_blocks", "no"}, |
| {"rocksdb.metadata_block_cache_size", "100"}, |
| {"rocksdb.subkey_block_cache_size", "100"}, |
| {"rocksdb.row_cache_size", "100"}, |
| {"rocksdb.rate_limiter_auto_tuned", "yes"}, |
| {"rocksdb.compression_level", "32767"}, |
| {"rocksdb.compression_max_dict_bytes", "16384"}, |
| {"rocksdb.compression_zstd_max_train_bytes", "262144"}, |
| {"rocksdb.wal_compression", "no"}, |
| {"histogram-bucket-boundaries", "10,100,1000,10000"}, |
| |
| }; |
| for (const auto &iter : immutable_cases) { |
| s = config.Set(nullptr, iter.first, iter.second); |
| ASSERT_FALSE(s.IsOK()); |
| } |
| } |
| |
| TEST(Config, GetRenameCommand) { |
| const char *path = "test.conf"; |
| unlink(path); |
| |
| std::ofstream output_file(path, std::ios::out); |
| output_file << "rename-command KEYS KEYS_NEW" << "\n"; |
| output_file << "rename-command GET GET_NEW" << "\n"; |
| output_file << "rename-command SET SET_NEW" << "\n"; |
| output_file.close(); |
| redis::CommandTable::Reset(); |
| Config config; |
| ASSERT_TRUE(config.Load(CLIOptions(path)).IsOK()); |
| std::vector<std::string> values; |
| config.Get("rename-command", &values); |
| ASSERT_EQ(values[1], "KEYS KEYS_NEW"); |
| ASSERT_EQ(values[3], "GET GET_NEW"); |
| ASSERT_EQ(values[5], "SET SET_NEW"); |
| ASSERT_EQ(values[0], "rename-command"); |
| ASSERT_EQ(values[2], "rename-command"); |
| ASSERT_EQ(values[4], "rename-command"); |
| } |
| |
| TEST(Config, Rewrite) { |
| const char *path = "test.conf"; |
| unlink(path); |
| |
| std::ofstream output_file(path, std::ios::out); |
| output_file << "rename-command KEYS KEYS_NEW" << "\n"; |
| output_file << "rename-command GET GET_NEW" << "\n"; |
| output_file << "rename-command SET SET_NEW" << "\n"; |
| output_file.close(); |
| |
| redis::CommandTable::Reset(); |
| Config config; |
| ASSERT_TRUE(config.Load(CLIOptions(path)).IsOK()); |
| ASSERT_EQ(config.dir + "/backup", config.backup_dir); |
| ASSERT_EQ(config.dir + "/kvrocks.pid", config.pidfile); |
| ASSERT_TRUE(config.Rewrite({}).IsOK()); |
| // Need to re-populate the command table since it has renamed by the previous |
| redis::CommandTable::Reset(); |
| Config new_config; |
| ASSERT_TRUE(new_config.Load(CLIOptions(path)).IsOK()); |
| unlink(path); |
| } |
| |
| TEST(Config, LoadRocksDBDictionaryCompressionOptions) { |
| const char *path = "test.conf"; |
| unlink(path); |
| |
| std::ofstream output_file(path, std::ios::out); |
| output_file << "rocksdb.compression_max_dict_bytes 16384" |
| << "\n"; |
| output_file << "rocksdb.compression_zstd_max_train_bytes 262144" |
| << "\n"; |
| output_file.close(); |
| |
| Config config; |
| ASSERT_TRUE(config.Load(CLIOptions(path)).IsOK()); |
| ASSERT_EQ(config.rocks_db.compression_max_dict_bytes, 16384U); |
| ASSERT_EQ(config.rocks_db.compression_zstd_max_train_bytes, 262144U); |
| |
| ASSERT_TRUE(config.Rewrite({}).IsOK()); |
| |
| Config rewritten_config; |
| ASSERT_TRUE(rewritten_config.Load(CLIOptions(path)).IsOK()); |
| ASSERT_EQ(rewritten_config.rocks_db.compression_max_dict_bytes, 16384U); |
| ASSERT_EQ(rewritten_config.rocks_db.compression_zstd_max_train_bytes, 262144U); |
| |
| unlink(path); |
| } |
| |
| TEST(Config, ParseConfigLine) { |
| ASSERT_EQ(*ParseConfigLine(""), ConfigKV{}); |
| ASSERT_EQ(*ParseConfigLine("# hello"), ConfigKV{}); |
| ASSERT_EQ(*ParseConfigLine(" #x y z "), ConfigKV{}); |
| ASSERT_EQ(*ParseConfigLine("key value "), (ConfigKV{"key", "value"})); |
| ASSERT_EQ(*ParseConfigLine("key value#x"), (ConfigKV{"key", "value"})); |
| ASSERT_EQ(*ParseConfigLine("key"), (ConfigKV{"key", ""})); |
| ASSERT_EQ(*ParseConfigLine(" key value1 value2 "), (ConfigKV{"key", "value1 value2"})); |
| ASSERT_EQ(*ParseConfigLine(" #"), ConfigKV{}); |
| ASSERT_EQ(*ParseConfigLine(" key val ue #h e l l o"), (ConfigKV{"key", "val ue"})); |
| ASSERT_EQ(*ParseConfigLine("key 'val ue'"), (ConfigKV{"key", "val ue"})); |
| ASSERT_EQ(*ParseConfigLine(R"(key ' value\'\'v a l ')"), (ConfigKV{"key", " value''v a l "})); |
| ASSERT_EQ(*ParseConfigLine(R"( key "val # hi" # hello!)"), (ConfigKV{"key", "val # hi"})); |
| ASSERT_EQ(*ParseConfigLine(R"(key "\n \r \t ")"), (ConfigKV{"key", "\n \r \t "})); |
| ASSERT_EQ(*ParseConfigLine("key ''"), (ConfigKV{"key", ""})); |
| ASSERT_FALSE(ParseConfigLine("key \"hello ")); |
| ASSERT_FALSE(ParseConfigLine("key \'\\")); |
| ASSERT_FALSE(ParseConfigLine("key \"hello'")); |
| ASSERT_FALSE(ParseConfigLine("key \"")); |
| ASSERT_FALSE(ParseConfigLine("key '' ''")); |
| ASSERT_FALSE(ParseConfigLine("key '' x")); |
| } |
| |
| TEST(Config, DumpConfigLine) { |
| ASSERT_EQ(DumpConfigLine({"key", "value"}), "key value"); |
| ASSERT_EQ(DumpConfigLine({"key", " v a l "}), R"(key " v a l ")"); |
| ASSERT_EQ(DumpConfigLine({"a", "'b"}), "a \"\\'b\""); |
| ASSERT_EQ(DumpConfigLine({"a", "x#y"}), "a \"x#y\""); |
| ASSERT_EQ(DumpConfigLine({"a", "x y"}), "a \"x y\""); |
| ASSERT_EQ(DumpConfigLine({"a", "xy"}), "a xy"); |
| ASSERT_EQ(DumpConfigLine({"a", "x\n"}), "a \"x\\n\""); |
| } |
| |
| TEST(Config, DisableL0Slowdown) { |
| Config config; |
| config.db_dir = "test_l0_slowdown_dir"; |
| |
| std::error_code ec; |
| std::filesystem::remove_all(config.db_dir, ec); |
| ASSERT_TRUE(!ec); |
| |
| auto storage = std::make_unique<engine::Storage>(&config); |
| ASSERT_TRUE(storage->Open().IsOK()); |
| |
| Server server(storage.get(), &config); |
| |
| const std::string kL0SlowdownWritesTrigger = "rocksdb.level0_slowdown_writes_trigger"; |
| const std::string kL0StopWritesTrigger = "rocksdb.level0_stop_writes_trigger"; |
| |
| const auto slowdown_is = [&storage](int value) { |
| return storage->GetDB()->GetOptions().level0_slowdown_writes_trigger == value; |
| }; |
| |
| const auto stop_is = [&storage](int value) { |
| return storage->GetDB()->GetOptions().level0_stop_writes_trigger == value; |
| }; |
| |
| ASSERT_TRUE(config.Set(&server, kL0StopWritesTrigger, "20").IsOK()); |
| ASSERT_TRUE(config.Set(&server, kL0SlowdownWritesTrigger, "0").IsOK()); |
| ASSERT_TRUE(slowdown_is(20)); |
| ASSERT_TRUE(stop_is(20)); |
| |
| ASSERT_TRUE(config.Set(&server, kL0StopWritesTrigger, "50").IsOK()); |
| ASSERT_TRUE(slowdown_is(50)); |
| ASSERT_TRUE(stop_is(50)); |
| |
| ASSERT_TRUE(config.Set(&server, kL0SlowdownWritesTrigger, "20").IsOK()); |
| ASSERT_TRUE(slowdown_is(20)); |
| ASSERT_TRUE(stop_is(50)); |
| |
| ASSERT_TRUE(config.Set(&server, kL0SlowdownWritesTrigger, "0").IsOK()); |
| ASSERT_TRUE(slowdown_is(50)); |
| ASSERT_TRUE(stop_is(50)); |
| } |