Skip to content

Commit

Permalink
Merge pull request #95 from tgockel/issue/90/four-letter-words
Browse files Browse the repository at this point in the history
server: Adds configuration support for four letter words.
  • Loading branch information
tgockel authored Jul 6, 2018
2 parents fff88fc + 63b8caf commit 5990676
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 20 deletions.
109 changes: 91 additions & 18 deletions src/zk/server/configuration.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "configuration.hpp"

#include <cctype>
#include <cstdlib>
#include <fstream>
#include <istream>
Expand Down Expand Up @@ -54,6 +55,9 @@ const configuration::duration_type configuration::default_tick_time = std::chron
const std::size_t configuration::default_init_limit = 10U;
const std::size_t configuration::default_sync_limit = 5U;

const std::set<std::string> configuration::default_four_letter_word_whitelist = { "srvr" };
const std::set<std::string> configuration::all_four_letter_word_whitelist = { "*" };

template <typename T>
configuration::setting<T>::setting() noexcept :
value(nullopt),
Expand Down Expand Up @@ -81,6 +85,36 @@ configuration configuration::make_minimal(std::string data_directory, std::uint1
return out;
}

static std::set<std::string> parse_whitelist(string_view source)
{
std::set<std::string> out;

while (!source.empty())
{
auto idx = source.find_first_of(',');
if (idx == string_view::npos)
idx = source.size();

if (idx == 0)
{
source.remove_prefix(1);
continue;
}

auto sub = source.substr(0, idx);
source.remove_prefix(idx);
while (!sub.empty() && std::isspace(sub.front()))
sub.remove_prefix(1);

while (!sub.empty() && std::isspace(sub.back()))
sub.remove_suffix(1);

out.insert(std::string(sub));
}

return out;
}

configuration configuration::from_lines(std::vector<std::string> lines)
{
static const std::regex line_expr(R"(^([^=]+)=([^ #]+)[ #]*$)",
Expand Down Expand Up @@ -128,6 +162,10 @@ configuration configuration::from_lines(std::vector<std::string> lines)
{
out._leader_serves = { (data == "yes"), line_no };
}
else if (name == "4lw.commands.whitelist")
{
out._four_letter_word_whitelist = { parse_whitelist(data), line_no };
}
else if (name.find("server.") == 0U)
{
auto id = std::size_t(std::atol(name.c_str() + 7));
Expand Down Expand Up @@ -273,9 +311,9 @@ configuration& configuration::sync_limit(optional<std::size_t> limit)
return *this;
}

optional<bool> configuration::leader_serves() const
bool configuration::leader_serves() const
{
return _leader_serves.value;
return _leader_serves.value.value_or(true);
}

configuration& configuration::leader_serves(optional<bool> serve)
Expand All @@ -284,6 +322,40 @@ configuration& configuration::leader_serves(optional<bool> serve)
return *this;
}

const std::set<std::string>& configuration::four_letter_word_whitelist() const
{
if (_four_letter_word_whitelist.value)
return *_four_letter_word_whitelist.value;
else
return default_four_letter_word_whitelist;
}

configuration& configuration::four_letter_word_whitelist(optional<std::set<std::string>> words)
{
if (words && words->size() > 1U && words->count("*"))
throw std::invalid_argument("");

set(_four_letter_word_whitelist,
std::move(words),
"4lw.commands.whitelist",
[] (const std::set<std::string>& words)
{
bool first = true;
std::ostringstream os;

for (const auto& word : words)
{
if (!std::exchange(first, false))
os << ',';
os << word;
}

return os.str();
}
);
return *this;
}

std::map<server_id, std::string> configuration::servers() const
{
std::map<server_id, std::string> out;
Expand Down Expand Up @@ -367,22 +439,23 @@ bool operator==(const configuration& lhs, const configuration& rhs)
&& a.second.value == b.second.value;
};

return lhs.client_port() == rhs.client_port()
&& lhs.data_directory() == rhs.data_directory()
&& lhs.tick_time() == rhs.tick_time()
&& lhs.init_limit() == rhs.init_limit()
&& lhs.sync_limit() == rhs.sync_limit()
&& lhs.leader_serves() == rhs.leader_serves()
&& lhs._server_paths.size() == rhs._server_paths.size()
&& lhs._server_paths.end() == std::mismatch(lhs._server_paths.begin(), lhs._server_paths.end(),
rhs._server_paths.begin(), rhs._server_paths.end(),
same_items
).first
&& lhs._unknown_settings.size() == rhs._unknown_settings.size()
&& lhs._unknown_settings.end() == std::mismatch(lhs._unknown_settings.begin(), lhs._unknown_settings.end(),
rhs._unknown_settings.begin(), rhs._unknown_settings.end(),
same_items
).first
return lhs.client_port() == rhs.client_port()
&& lhs.data_directory() == rhs.data_directory()
&& lhs.tick_time() == rhs.tick_time()
&& lhs.init_limit() == rhs.init_limit()
&& lhs.sync_limit() == rhs.sync_limit()
&& lhs.leader_serves() == rhs.leader_serves()
&& lhs.four_letter_word_whitelist() == rhs.four_letter_word_whitelist()
&& lhs._server_paths.size() == rhs._server_paths.size()
&& lhs._server_paths.end() == std::mismatch(lhs._server_paths.begin(), lhs._server_paths.end(),
rhs._server_paths.begin(), rhs._server_paths.end(),
same_items
).first
&& lhs._unknown_settings.size() == rhs._unknown_settings.size()
&& lhs._unknown_settings.end() == std::mismatch(lhs._unknown_settings.begin(), lhs._unknown_settings.end(),
rhs._unknown_settings.begin(), rhs._unknown_settings.end(),
same_items
).first
;
}

Expand Down
38 changes: 37 additions & 1 deletion src/zk/server/configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <iosfwd>
#include <string>
#include <map>
#include <set>
#include <vector>

namespace zk::server
Expand Down Expand Up @@ -91,6 +92,17 @@ class configuration final
/// The default value for \ref sync_limit.
static const std::size_t default_sync_limit;

/// The default value for \ref four_letter_word_whitelist.
static const std::set<std::string> default_four_letter_word_whitelist;

/// A value for \ref four_letter_word_whitelist that enables all commands. Note that this is not a list of all
/// allowed words, but simply the string \c "*".
static const std::set<std::string> all_four_letter_word_whitelist;

/// All known values allowed in \ref four_letter_word_whitelist. This set comes from what ZooKeeper server 3.5.3
/// supported, so it is possible the version of ZooKeeper you are running supports a different set.
static const std::set<std::string> known_four_letter_word_whitelist;

public:
/// Creates a minimal configuration, setting the four needed values. The resulting \ref configuration can be run
/// through a file with \c save or it can run directly from the command line.
Expand Down Expand Up @@ -153,10 +165,33 @@ class configuration final
/// Should an elected leader accepts client connections? For higher update throughput at the slight expense of read
/// latency, the leader can be configured to not accept clients and focus on coordination. The default to this value
/// is \c true, which means that a leader will accept client connections.
optional<bool> leader_serves() const;
bool leader_serves() const;
configuration& leader_serves(optional<bool> serve);
/// \}

/// \{
/// A list of comma separated four letter words commands that user wants to use. A valid four letter words command
/// must be put in this list or the ZooKeeper server will not enable the command. If unspecified, the whitelist only
/// contains "srvr" command (\ref default_four_letter_word_whitelist).
///
/// \note
/// It is planned that the ZooKeeper server will deprecate this whitelist in preference of using a JSON REST API for
/// health checks. It is unlikely to be deprecated any time in the near future and will likely remain in the product
/// for a very long time.
///
/// \param words is the list of four letter words to allow or \c nullopt to clear the setting. If specified as an
/// empty set, this explicitly disables all words, which is \e different than setting this value to \c nullopt.
///
/// \throws std::invalid_argument if \a words contains the all value (\c "*") but it is not the only value in the
/// set.
///
/// \ref default_four_letter_word_whitelist
/// \ref all_four_letter_word_whitelist
/// \ref known_four_letter_word_whitelist
const std::set<std::string>& four_letter_word_whitelist() const;
configuration& four_letter_word_whitelist(optional<std::set<std::string>> words);
/// \}

/// Get the servers which are part of the ZooKeeper ensemble.
std::map<server_id, std::string> servers() const;

Expand Down Expand Up @@ -232,6 +267,7 @@ class configuration final
setting<std::size_t> _init_limit;
setting<std::size_t> _sync_limit;
setting<bool> _leader_serves;
setting<std::set<std::string>> _four_letter_word_whitelist;
std::map<server_id, setting<std::string>> _server_paths;
std::map<std::string, setting<std::string>> _unknown_settings;
};
Expand Down
21 changes: 20 additions & 1 deletion src/zk/server/configuration_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ GTEST_TEST(configuration_tests, from_example)
CHECK_EQ(10U, parsed.init_limit());
CHECK_EQ(5U, parsed.sync_limit());
CHECK_EQ(2181U, parsed.client_port());
CHECK_TRUE(parsed.leader_serves().value());
CHECK_TRUE(parsed.leader_serves());

auto servers = parsed.servers();
CHECK_EQ(3U, servers.size());
Expand Down Expand Up @@ -80,4 +80,23 @@ GTEST_TEST(configuration_tests, minimal)
CHECK_TRUE(minimal.is_minimal());
}

static string_view configuration_source_with_four_letter_words_example =
R"(# http://hadoop.apache.org/zookeeper/docs/current/zookeeperAdmin.html
tickTime=2500
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper
4lw.commands.whitelist=stat,mntr,srvr,ruok
)";

GTEST_TEST(configuration_tests, four_letter_words)
{
auto parsed = configuration::from_string(configuration_source_with_four_letter_words_example);

CHECK_EQ(4U, parsed.four_letter_word_whitelist().size());
std::set<std::string> expected = { "stat", "mntr", "srvr", "ruok" };
CHECK_TRUE(expected == parsed.four_letter_word_whitelist());
}

}

0 comments on commit 5990676

Please sign in to comment.