zookeeper-cpp
ZooKeeper Client for C++
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Groups
configuration.cpp
1 #include "configuration.hpp"
2 
3 #include <cstdlib>
4 #include <fstream>
5 #include <istream>
6 #include <ostream>
7 #include <regex>
8 #include <sstream>
9 #include <stdexcept>
10 
11 namespace zk::server
12 {
13 
15 {
16  if (0U < value && value < 256U)
17  return;
18 
19  std::ostringstream os;
20  os << "Server ID value " << value << " is not in the valid range [1 .. 255]";
21  throw std::out_of_range(os.str());
22 }
23 
24 std::ostream& operator<<(std::ostream& os, const server_id& self)
25 {
26  return os << self.value;
27 }
28 
29 namespace
30 {
31 
32 constexpr std::size_t not_a_line = ~0UL;
33 
34 class zero_copy_streambuf final :
35  public std::streambuf
36 {
37 public:
38  zero_copy_streambuf(string_view input)
39  {
40  // We are just going to read from it, so this const_cast is okay
41  ptr<char> p = const_cast<ptr<char>>(input.data());
42  setg(p, p, p + input.size());
43  }
44 };
45 
46 }
47 
48 std::uint16_t configuration::default_client_port = std::uint16_t(2181);
49 std::uint16_t configuration::default_peer_port = std::uint16_t(2888);
50 std::uint16_t configuration::default_leader_port = std::uint16_t(3888);
51 
52 configuration::duration_type configuration::default_tick_time = std::chrono::milliseconds(2000);
53 
54 std::size_t configuration::default_init_limit = 10U;
55 std::size_t configuration::default_sync_limit = 5U;
56 
57 template <typename T>
58 configuration::setting<T>::setting() noexcept :
59  value(nullopt),
60  line(not_a_line)
61 { }
62 
63 template <typename T>
64 configuration::setting<T>::setting(T value, std::size_t line) noexcept :
65  value(std::move(value)),
66  line(line)
67 { }
68 
69 configuration::configuration() = default;
70 
71 configuration::~configuration() noexcept = default;
72 
73 configuration configuration::make_minimal(std::string data_directory, std::uint16_t client_port)
74 {
75  configuration out;
76  out.data_directory(std::move(data_directory))
77  .client_port(client_port)
78  .init_limit(default_init_limit)
79  .sync_limit(default_sync_limit)
80  ;
81  return out;
82 }
83 
84 configuration configuration::from_lines(std::vector<std::string> lines)
85 {
86  static const std::regex line_expr(R"(^([^=]+)=([^ #]+)[ #]*$)",
87  std::regex_constants::ECMAScript | std::regex_constants::optimize
88  );
89  constexpr auto name_idx = 1U;
90  constexpr auto data_idx = 2U;
91 
92  configuration out;
93 
94  for (std::size_t line_no = 0U; line_no < lines.size(); ++line_no)
95  {
96  const auto& line = lines[line_no];
97 
98  if (line.empty() || line[0] == '#')
99  continue;
100 
101  std::smatch match;
102  if (std::regex_match(line, match, line_expr))
103  {
104  auto name = match[name_idx].str();
105  auto data = match[data_idx].str();
106 
107  if (name == "clientPort")
108  {
109  out._client_port = { std::uint16_t(std::atoi(data.c_str())), line_no };
110  }
111  else if (name == "dataDir")
112  {
113  out._data_directory = { std::move(data), line_no };
114  }
115  else if (name == "tickTime")
116  {
117  out._tick_time = { std::chrono::milliseconds(std::atol(data.c_str())), line_no };
118  }
119  else if (name == "initLimit")
120  {
121  out._init_limit = { std::size_t(std::atol(data.c_str())), line_no };
122  }
123  else if (name == "syncLimit")
124  {
125  out._sync_limit = { std::size_t(std::atol(data.c_str())), line_no };
126  }
127  else if (name == "leaderServes")
128  {
129  out._leader_serves = { (data == "yes"), line_no };
130  }
131  else if (name.find("server.") == 0U)
132  {
133  auto id = std::size_t(std::atol(name.c_str() + 7));
134  out._server_paths.insert({ server_id(id), { std::move(data), line_no } });
135  }
136  else
137  {
138  out._unknown_settings.insert({ std::move(name), { std::move(data), line_no } });
139  }
140  }
141  }
142 
143  out._lines = std::move(lines);
144  return out;
145 }
146 
148 {
149  std::vector<std::string> lines;
150  std::string line;
151 
152  while(std::getline(stream, line))
153  {
154  lines.emplace_back(std::move(line));
155  }
156 
157  if (stream.eof())
158  return from_lines(std::move(lines));
159  else
160  throw std::runtime_error("Loading configuration did not reach EOF");
161 }
162 
164 {
165  std::ifstream inf(filename.c_str());
166  auto out = from_stream(inf);
167  out._source_file = std::move(filename);
168  return out;
169 }
170 
172 {
173  zero_copy_streambuf buff(value);
174  std::istream stream(&buff);
175  return from_stream(stream);
176 }
177 
179 {
180  return _data_directory.value
181  && _client_port.value
182  && _init_limit.value && init_limit() == default_init_limit
183  && _sync_limit.value && sync_limit() == default_sync_limit
184  && _lines.size() == 4U;
185 }
186 
187 template <typename T, typename FEncode>
188 void configuration::set(setting<T>& target, optional<T> value, string_view key, const FEncode& encode)
189 {
190  std::string target_line;
191  if (value)
192  {
193  std::ostringstream os;
194  os << key << '=' << encode(*value);
195  target_line = os.str();
196  }
197 
198  if (target.line == not_a_line && value)
199  {
200  target.value = std::move(value);
201  target.line = _lines.size();
202  _lines.emplace_back(std::move(target_line));
203  }
204  else if (target.line == not_a_line && !value)
205  {
206  // do nothing -- no line means no value
207  }
208  else
209  {
210  target.value = std::move(value);
211  _lines[target.line] = std::move(target_line);
212  }
213 }
214 
215 template <typename T>
216 void configuration::set(setting<T>& target, optional<T> value, string_view key)
217 {
218  return set(target, std::move(value), key, [] (const T& x) -> const T& { return x; });
219 }
220 
221 std::uint16_t configuration::client_port() const
222 {
223  return _client_port.value.value_or(default_client_port);
224 }
225 
226 configuration& configuration::client_port(optional<std::uint16_t> port)
227 {
228  set(_client_port, port, "clientPort");
229  return *this;
230 }
231 
232 const optional<std::string>& configuration::data_directory() const
233 {
234  return _data_directory.value;
235 }
236 
237 configuration& configuration::data_directory(optional<std::string> path)
238 {
239  set(_data_directory, std::move(path), "dataDir");
240  return *this;
241 }
242 
243 configuration::duration_type configuration::tick_time() const
244 {
245  return _tick_time.value.value_or(default_tick_time);
246 }
247 
248 configuration& configuration::tick_time(optional<duration_type> tick_time)
249 {
250  set(_tick_time, tick_time, "tickTime", [] (duration_type x) { return x.count(); });
251  return *this;
252 }
253 
254 std::size_t configuration::init_limit() const
255 {
256  return _init_limit.value.value_or(default_init_limit);
257 }
258 
259 configuration& configuration::init_limit(optional<std::size_t> limit)
260 {
261  set(_init_limit, limit, "initLimit");
262  return *this;
263 }
264 
265 std::size_t configuration::sync_limit() const
266 {
267  return _sync_limit.value.value_or(default_sync_limit);
268 }
269 
270 configuration& configuration::sync_limit(optional<std::size_t> limit)
271 {
272  set(_sync_limit, limit, "syncLimit");
273  return *this;
274 }
275 
276 optional<bool> configuration::leader_serves() const
277 {
278  return _leader_serves.value;
279 }
280 
281 configuration& configuration::leader_serves(optional<bool> serve)
282 {
283  set(_leader_serves, serve, "leaderServes", [] (bool x) { return x ? "yes" : "no"; });
284  return *this;
285 }
286 
287 std::map<server_id, std::string> configuration::servers() const
288 {
289  std::map<server_id, std::string> out;
290  for (const auto& entry : _server_paths)
291  out.insert({ entry.first, *entry.second.value });
292 
293  return out;
294 }
295 
297  std::string hostname,
298  std::uint16_t peer_port,
299  std::uint16_t leader_port
300  )
301 {
302  id.ensure_valid();
303 
304  if (_server_paths.count(id))
305  throw std::runtime_error(std::string("Already a server with ID ") + std::to_string(id.value));
306 
307  hostname += ":";
308  hostname += std::to_string(peer_port);
309  hostname += ":";
310  hostname += std::to_string(leader_port);
311 
312  auto iter = _server_paths.emplace(id, setting<std::string>()).first;
313  set(iter->second, some(std::move(hostname)), std::string("server.") + std::to_string(iter->first.value));
314  return *this;
315 }
316 
317 std::map<std::string, std::string> configuration::unknown_settings() const
318 {
319  std::map<std::string, std::string> out;
320  for (const auto& entry : _unknown_settings)
321  out.insert({ entry.first, *entry.second.value });
322 
323  return out;
324 }
325 
326 configuration& configuration::add_setting(std::string key, std::string value)
327 {
328  // This process is really inefficient, but people should not be using this very often. This is done this way because
329  // it is possible to specify a key that has a known setting (such as "dataDir"), which needs to be picked up
330  // correctly. `from_lines` has this logic, so just use it. Taking that matching logic out would be the best approach
331  // to take, but since this shouldn't be used, I haven't bothered.
332  auto source_file = _source_file;
333  auto lines = _lines;
334  lines.emplace_back(key + "=" + value);
335 
336  *this = configuration::from_lines(std::move(lines));
337  _source_file = std::move(source_file);
338  return *this;
339 }
340 
341 void configuration::save(std::ostream& os) const
342 {
343  for (const auto& line : _lines)
344  os << line << std::endl;
345 
346  os.flush();
347 }
348 
349 void configuration::save_file(std::string filename)
350 {
351  std::ofstream ofs(filename.c_str());
352  save(ofs);
353  if (ofs)
354  _source_file = std::move(filename);
355  else
356  throw std::runtime_error("Error saving file");
357 }
358 
359 bool operator==(const configuration& lhs, const configuration& rhs)
360 {
361  if (&lhs == &rhs)
362  return true;
363 
364  auto same_items = [] (const auto& a, const auto& b)
365  {
366  return a.first == b.first
367  && a.second.value == b.second.value;
368  };
369 
370  return lhs.client_port() == rhs.client_port()
371  && lhs.data_directory() == rhs.data_directory()
372  && lhs.tick_time() == rhs.tick_time()
373  && lhs.init_limit() == rhs.init_limit()
374  && lhs.sync_limit() == rhs.sync_limit()
375  && lhs.leader_serves() == rhs.leader_serves()
376  && lhs._server_paths.size() == rhs._server_paths.size()
377  && lhs._server_paths.end() == std::mismatch(lhs._server_paths.begin(), lhs._server_paths.end(),
378  rhs._server_paths.begin(), rhs._server_paths.end(),
379  same_items
380  ).first
381  && lhs._unknown_settings.size() == rhs._unknown_settings.size()
382  && lhs._unknown_settings.end() == std::mismatch(lhs._unknown_settings.begin(), lhs._unknown_settings.end(),
383  rhs._unknown_settings.begin(), rhs._unknown_settings.end(),
384  same_items
385  ).first
386  ;
387 }
388 
389 bool operator!=(const configuration& lhs, const configuration& rhs)
390 {
391  return !(lhs == rhs);
392 }
393 
394 }
T * ptr
A simple, unowned pointer.
Definition: config.hpp:37
std::map< server_id, std::string > servers() const
Get the servers which are part of the ZooKeeper ensemble.
Represents the ID of a server in the ensemble.
const optional< std::string > & source_file() const
Get the source file. This will only have a value if this was created by from_file.
bool is_minimal() const
Check if this is a "minimal" configuration – meaning it only has a data_directory and client_port se...
const optional< std::string > & data_directory() const
static configuration from_lines(std::vector< std::string > lines)
Load configuration from the provided lines.
static std::uint16_t default_leader_port
The default value for leader_port.
static std::uint16_t default_client_port
The default value for client_port.
static std::size_t default_sync_limit
The default value for sync_limit.
std::map< std::string, std::string > unknown_settings() const
Get settings that were in the configuration file (or manually added with add_setting) but unknown to ...
static configuration from_file(std::string filename)
Load the configuration from a file.
static configuration from_stream(std::istream &stream)
Load configuration from the provided stream.
duration_type tick_time() const
static configuration from_string(string_view value)
Load configuration directly from the in-memory value.
std::size_t init_limit() const
static std::size_t default_init_limit
The default value for init_limit.
void ensure_valid() const
Check that this ID is a valid one.
static duration_type default_tick_time
The default value for tick_time.
Represents a configuration which should be run by server instance.
void save(std::ostream &stream) const
Write this configuration to the provided stream.
friend bool operator==(const configuration &lhs, const configuration &rhs)
void save_file(std::string filename)
Save this configuration to filename.
static std::uint16_t default_peer_port
The default value for peer_port.
std::size_t sync_limit() const
std::uint16_t client_port() const
optional< bool > leader_serves() const
configuration & add_server(server_id id, std::string hostname, std::uint16_t peer_port=default_peer_port, std::uint16_t leader_port=default_leader_port)
Add a new server to the configuration.
value_type value
Underlying value of this ID.
Definition: types.hpp:34
configuration & add_setting(std::string key, std::string value)
Add an arbitrary setting with the key and value.