1 /*
2   Copyright (c) 2017, 2020, Oracle and/or its affiliates.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include <condition_variable>
26 #include <csignal>
27 #include <fstream>
28 #include <functional>
29 #include <mutex>
30 #include <string>
31 #include <thread>
32 
33 #include "dim.h"
34 #include "mock_server_rest_client.h"
35 #include "mock_server_testutils.h"
36 #include "mysql/harness/logging/logging.h"
37 #include "mysql_session.h"
38 #include "mysqlrouter/utils.h"
39 #include "random_generator.h"
40 #include "router_component_test.h"
41 #include "tcp_port_pool.h"
42 
43 /**
44  * @file
45  * @brief Component Tests for loggers
46  */
47 
48 using mysql_harness::logging::LogLevel;
49 using mysql_harness::logging::LogTimestampPrecision;
50 using testing::HasSubstr;
51 using testing::Not;
52 using testing::StartsWith;
53 using namespace std::chrono_literals;
54 
55 class RouterLoggingTest : public RouterComponentTest {
56  protected:
create_config_file(const std::string & directory,const std::string & sections,const std::map<std::string,std::string> * default_section) const57   std::string create_config_file(
58       const std::string &directory, const std::string &sections,
59       const std::map<std::string, std::string> *default_section) const {
60     return ProcessManager::create_config_file(
61         directory, sections, default_section, "mysqlrouter.conf", "", false);
62   }
63 
64   TcpPortPool port_pool_;
65 };
66 
67 /** @test This test verifies that fatal error messages thrown before switching
68  * to logger specified in config file (before Loader::run() runs
69  * logger_plugin.cc:init()) are properly logged to STDERR
70  */
TEST_F(RouterLoggingTest,log_startup_failure_to_console)71 TEST_F(RouterLoggingTest, log_startup_failure_to_console) {
72   auto conf_params = get_DEFAULT_defaults();
73   // we want to log to the console
74   conf_params["logging_folder"] = "";
75   TempDirectory conf_dir("conf");
76   const std::string conf_file =
77       create_config_file(conf_dir.name(), "[invalid]", &conf_params);
78 
79   // run the router and wait for it to exit
80   auto &router = launch_router({"-c", conf_file}, 1);
81   check_exit_code(router, EXIT_FAILURE);
82 
83   // expect something like this to appear on STDERR
84   // plugin 'invalid' failed to
85   // load: ./plugin_output_directory/invalid.so: cannot open shared object
86   // file: No such file or directory
87   const std::string out = router.get_full_output();
88   EXPECT_THAT(
89       out, HasSubstr("Loading plugin for config-section '[invalid]' failed"));
90 }
91 
92 /** @test This test is similar to log_startup_failure_to_console(), but the
93  * failure message is expected to be logged into a logfile
94  */
TEST_F(RouterLoggingTest,log_startup_failure_to_logfile)95 TEST_F(RouterLoggingTest, log_startup_failure_to_logfile) {
96   // create tmp dir where we will log
97   TempDirectory logging_folder;
98 
99   // create config with logging_folder set to that directory
100   std::map<std::string, std::string> params = get_DEFAULT_defaults();
101   params.at("logging_folder") = logging_folder.name();
102   TempDirectory conf_dir("conf");
103   const std::string conf_file =
104       create_config_file(conf_dir.name(), "[routing]", &params);
105 
106   // run the router and wait for it to exit
107   auto &router = launch_router({"-c", conf_file}, 1);
108   check_exit_code(router, EXIT_FAILURE);
109 
110   // expect something like this to appear in log:
111   // 2018-12-19 03:54:04 main ERROR [7f539f628780] Configuration error: option
112   // destinations in [routing] is required
113   auto matcher = [](const std::string &line) -> bool {
114     return line.find(
115                "Configuration error: option destinations in [routing] is "
116                "required") != line.npos;
117   };
118 
119   EXPECT_TRUE(find_in_file(logging_folder.name() + "/mysqlrouter.log", matcher))
120       << "log:"
121       << router.get_full_logfile("mysqlrouter.log", logging_folder.name());
122 }
123 
124 /** @test This test verifies that invalid logging_folder is properly handled and
125  * appropriate message is printed on STDERR. Router tries to
126  * mkdir(logging_folder) if it doesn't exist, then write its log inside of it.
127  */
TEST_F(RouterLoggingTest,bad_logging_folder)128 TEST_F(RouterLoggingTest, bad_logging_folder) {
129   // create tmp dir to contain our tests
130   TempDirectory tmp_dir;
131 
132 // unfortunately it's not (reasonably) possible to make folders read-only on
133 // Windows, therefore we can run the following 2 tests only on Unix
134 // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
135 #ifndef _WIN32
136 
137   // make tmp dir read-only
138   chmod(tmp_dir.name().c_str(),
139         S_IRUSR | S_IXUSR);  // r-x for the user (aka 500)
140 
141   // logging_folder doesn't exist and can't be created
142   {
143     const std::string logging_dir = tmp_dir.name() + "/some_dir";
144 
145     // create Router config
146     std::map<std::string, std::string> params = get_DEFAULT_defaults();
147     params.at("logging_folder") = logging_dir;
148     TempDirectory conf_dir("conf");
149     const std::string conf_file =
150         create_config_file(conf_dir.name(), "[keepalive]\n", &params);
151 
152     // run the router and wait for it to exit
153     auto &router = launch_router({"-c", conf_file}, 1);
154     check_exit_code(router, EXIT_FAILURE);
155 
156     // expect something like this to appear on STDERR
157     // Error: Error when creating dir '/bla': 13
158     const std::string out = router.get_full_output();
159     EXPECT_THAT(
160         out.c_str(),
161         HasSubstr("plugin 'logger' init failed: Error when creating dir '" +
162                   logging_dir + "': 13"));
163   }
164 
165   // logging_folder exists but is not writeable
166   {
167     const std::string logging_dir = tmp_dir.name();
168 
169     // create Router config
170     std::map<std::string, std::string> params = get_DEFAULT_defaults();
171     params.at("logging_folder") = logging_dir;
172     TempDirectory conf_dir("conf");
173     const std::string conf_file =
174         create_config_file(conf_dir.name(), "[keepalive]\n", &params);
175 
176     // run the router and wait for it to exit
177     auto &router = launch_router({"-c", conf_file}, 1);
178     check_exit_code(router, EXIT_FAILURE);
179 
180     // expect something like this to appear on STDERR
181     // Error: Cannot create file in directory //mysqlrouter.log: Permission
182     // denied
183     const std::string out = router.get_full_output();
184 #ifndef _WIN32
185     EXPECT_THAT(
186         out.c_str(),
187         HasSubstr(
188             "plugin 'logger' init failed: Cannot create file in directory " +
189             logging_dir + ": Permission denied\n"));
190 #endif
191   }
192 
193   // restore writability to tmp dir
194   chmod(tmp_dir.name().c_str(),
195         S_IRUSR | S_IWUSR | S_IXUSR);  // rwx for the user (aka 700)
196 
197 #endif  // #ifndef _WIN32
198 
199   // logging_folder is really a file
200   {
201     const std::string logging_dir = tmp_dir.name() + "/some_file";
202 
203     // create that file
204     {
205       std::ofstream some_file(logging_dir);
206       EXPECT_TRUE(some_file.good());
207     }
208 
209     // create Router config
210     std::map<std::string, std::string> params = get_DEFAULT_defaults();
211     params.at("logging_folder") = logging_dir;
212     TempDirectory conf_dir("conf");
213     const std::string conf_file =
214         create_config_file(conf_dir.name(), "[keepalive]\n", &params);
215 
216     // run the router and wait for it to exit
217     auto &router = launch_router({"-c", conf_file}, 1);
218     check_exit_code(router, EXIT_FAILURE);
219 
220     // expect something like this to appear on STDERR
221     // Error: Cannot create file in directory /etc/passwd/mysqlrouter.log: Not a
222     // directory
223     const std::string out = router.get_full_output();
224     const std::string prefix("Cannot create file in directory " + logging_dir +
225                              ": ");
226 #ifndef _WIN32
227     EXPECT_THAT(out.c_str(), HasSubstr(prefix + "Not a directory\n"));
228 #else
229     // on Windows emulate (wine) we get ENOTDIR
230     // with native windows we get ENOENT
231 
232     EXPECT_THAT(
233         out.c_str(),
234         ::testing::AnyOf(
235             ::testing::HasSubstr(prefix + "Directory name invalid.\n"),
236             ::testing::HasSubstr(
237                 prefix + "The system cannot find the path specified.\n")));
238 #endif
239   }
240 }
241 
TEST_F(RouterLoggingTest,multiple_logger_sections)242 TEST_F(RouterLoggingTest, multiple_logger_sections) {
243   // This test verifies that multiple [logger] sections are handled properly.
244   // Router should report the error on STDERR and exit
245 
246   auto conf_params = get_DEFAULT_defaults();
247   // we want to log to the console
248   conf_params["logging_folder"] = "";
249   TempDirectory conf_dir("conf");
250   const std::string conf_file =
251       create_config_file(conf_dir.name(), "[logger]\n[logger]\n", &conf_params);
252 
253   // run the router and wait for it to exit
254   auto &router = launch_router({"-c", conf_file}, 1);
255   check_exit_code(router, EXIT_FAILURE);
256 
257   // expect something like this to appear on STDERR
258   // Error: Configuration error: Section 'logger' already exists
259   const std::string out = router.get_full_output();
260   EXPECT_THAT(
261       out.c_str(),
262       ::testing::HasSubstr(
263           "Error: Configuration error: Section 'logger' already exists"));
264 }
265 
TEST_F(RouterLoggingTest,logger_section_with_key)266 TEST_F(RouterLoggingTest, logger_section_with_key) {
267   // This test verifies that [logger:with_some_key] section is handled properly
268   // Router should report the error on STDERR and exit
269   auto conf_params = get_DEFAULT_defaults();
270   // we want to log to the console
271   conf_params["logging_folder"] = "";
272   TempDirectory conf_dir("conf");
273   const std::string conf_file =
274       create_config_file(conf_dir.name(), "[logger:some_key]\n", &conf_params);
275 
276   // run the router and wait for it to exit
277   auto &router = launch_router({"-c", conf_file}, 1);
278   check_exit_code(router, EXIT_FAILURE);
279 
280   // expect something like this to appear on STDERR
281   // Error: Section 'logger' does not support key
282   const std::string out = router.get_full_output();
283   EXPECT_THAT(out.c_str(),
284               HasSubstr("Error: Section 'logger' does not support keys"));
285 }
286 
TEST_F(RouterLoggingTest,bad_loglevel)287 TEST_F(RouterLoggingTest, bad_loglevel) {
288   // This test verifies that bad log level in [logger] section is handled
289   // properly. Router should report the error on STDERR and exit
290 
291   auto conf_params = get_DEFAULT_defaults();
292   // we want to log to the console
293   conf_params["logging_folder"] = "";
294   TempDirectory conf_dir("conf");
295   const std::string conf_file = create_config_file(
296       conf_dir.name(), "[logger]\nlevel = UNKNOWN\n", &conf_params);
297 
298   // run the router and wait for it to exit
299   auto &router = launch_router({"-c", conf_file}, 1);
300   check_exit_code(router, EXIT_FAILURE);
301 
302   // expect something like this to appear on STDERR
303   // Configuration error: Log level 'unknown' is not valid. Valid values are:
304   // debug, error, fatal, info, note, system, and warning
305   const std::string out = router.get_full_output();
306   EXPECT_THAT(
307       out.c_str(),
308       HasSubstr(
309           "Configuration error: Log level 'unknown' is not valid. Valid "
310           "values are: debug, error, fatal, info, note, system, and warning"));
311 }
312 
313 /**************************************************/
314 /* Tests for valid logger configurations          */
315 /**************************************************/
316 
317 struct LoggingConfigOkParams {
318   std::string logger_config;
319   bool logging_folder_empty;
320 
321   LogLevel consolelog_expected_level;
322   LogLevel filelog_expected_level;
323 
324   LogTimestampPrecision consolelog_expected_timestamp_precision;
325   LogTimestampPrecision filelog_expected_timestamp_precision;
326 
LoggingConfigOkParamsLoggingConfigOkParams327   LoggingConfigOkParams(const std::string &logger_config_,
328                         const bool logging_folder_empty_,
329                         const LogLevel consolelog_expected_level_,
330                         const LogLevel filelog_expected_level_)
331       : logger_config(logger_config_),
332         logging_folder_empty(logging_folder_empty_),
333         consolelog_expected_level(consolelog_expected_level_),
334         filelog_expected_level(filelog_expected_level_),
335         consolelog_expected_timestamp_precision(LogTimestampPrecision::kNotSet),
336         filelog_expected_timestamp_precision(LogTimestampPrecision::kNotSet) {}
337 
LoggingConfigOkParamsLoggingConfigOkParams338   LoggingConfigOkParams(
339       const std::string &logger_config_, const bool logging_folder_empty_,
340       const LogLevel consolelog_expected_level_,
341       const LogLevel filelog_expected_level_,
342       const LogTimestampPrecision consolelog_expected_timestamp_precision_,
343       const LogTimestampPrecision filelog_expected_timestamp_precision_)
344       : logger_config(logger_config_),
345         logging_folder_empty(logging_folder_empty_),
346         consolelog_expected_level(consolelog_expected_level_),
347         filelog_expected_level(filelog_expected_level_),
348         consolelog_expected_timestamp_precision(
349             consolelog_expected_timestamp_precision_),
350         filelog_expected_timestamp_precision(
351             filelog_expected_timestamp_precision_) {}
352 };
353 
operator <<(::std::ostream & os,const LoggingConfigOkParams & ltp)354 ::std::ostream &operator<<(::std::ostream &os,
355                            const LoggingConfigOkParams &ltp) {
356   return os << "config=" << ltp.logger_config
357             << ", logging_folder_empty=" << ltp.logging_folder_empty;
358 }
359 
360 class RouterLoggingTestConfig
361     : public RouterLoggingTest,
362       public ::testing::WithParamInterface<LoggingConfigOkParams> {};
363 
364 /** @test This test verifies that a proper loggs are written to selected sinks
365  * for various sinks/levels combinations.
366  */
TEST_P(RouterLoggingTestConfig,LoggingTestConfig)367 TEST_P(RouterLoggingTestConfig, LoggingTestConfig) {
368   auto test_params = GetParam();
369 
370   TempDirectory tmp_dir;
371   TcpPortPool port_pool;
372   const auto router_port = port_pool.get_next_available();
373   const auto server_port = port_pool.get_next_available();
374 
375   // These are different level log entries that are expected to get logged after
376   // the logger plugin has been initialized
377   const std::string kDebugLogEntry = "plugin 'logger:' doesn't implement start";
378   const std::string kInfoLogEntry = "[routing] started: listening on 127.0.0.1";
379   const std::string kWarningLogEntry =
380       "Can't connect to remote MySQL server for client";
381   // System/Note does not produce unique output today
382   const std::string kNoteLogEntry = "";
383   const std::string kSystemLogEntry = "";
384 
385   // to trigger the warning entry in the log
386   const std::string kRoutingConfig =
387       "[routing]\n"
388       "bind_address=127.0.0.1:" +
389       std::to_string(router_port) +
390       "\n"
391       "destinations=localhost:" +
392       std::to_string(server_port) +
393       "\n"
394       "routing_strategy=round-robin\n";
395 
396   auto conf_params = get_DEFAULT_defaults();
397   conf_params["logging_folder"] =
398       test_params.logging_folder_empty ? "" : tmp_dir.name();
399 
400   TempDirectory conf_dir("conf");
401   const std::string conf_text =
402       test_params.logger_config + "\n" + kRoutingConfig;
403 
404   const std::string conf_file =
405       create_config_file(conf_dir.name(), conf_text, &conf_params);
406 
407   auto &router = launch_router({"-c", conf_file});
408 
409   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
410 
411   // try to make a connection; this will fail but should generate a warning in
412   // the logs
413   mysqlrouter::MySQLSession client;
414   try {
415     client.connect("127.0.0.1", router_port, "username", "password", "", "");
416   } catch (const std::exception &exc) {
417     if (std::string(exc.what()).find("Error connecting to MySQL server") !=
418         std::string::npos) {
419       // that's what we expect
420     } else
421       throw;
422   }
423 
424   const std::string console_log_txt = router.get_full_output();
425 
426   // check the console log if it contains what's expected
427   if (test_params.consolelog_expected_level >= LogLevel::kDebug &&
428       test_params.consolelog_expected_level != LogLevel::kNotSet) {
429     EXPECT_THAT(console_log_txt, HasSubstr(kDebugLogEntry)) << "console:\n"
430                                                             << console_log_txt;
431   } else {
432     EXPECT_THAT(console_log_txt, Not(HasSubstr(kDebugLogEntry)))
433         << "console:\n"
434         << console_log_txt;
435   }
436 
437   if (test_params.consolelog_expected_level >= LogLevel::kNote &&
438       test_params.consolelog_expected_level != LogLevel::kNotSet) {
439     // No NOTE output from Router today, so check that we see info
440     EXPECT_THAT(console_log_txt, HasSubstr(kInfoLogEntry)) << "console:\n"
441                                                            << console_log_txt;
442   } else {
443     // No NOTE output from Router today, so disable until Router does
444 #if 0
445     EXPECT_THAT(console_log_txt, Not(HasSubstr(kInfoLogEntry)))
446         << "console:\n"
447         << console_log_txt;
448 #endif
449   }
450 
451   if (test_params.consolelog_expected_level >= LogLevel::kInfo &&
452       test_params.consolelog_expected_level != LogLevel::kNotSet) {
453     EXPECT_THAT(console_log_txt, HasSubstr(kInfoLogEntry)) << "console:\n"
454                                                            << console_log_txt;
455   } else {
456     EXPECT_THAT(console_log_txt, Not(HasSubstr(kInfoLogEntry)))
457         << "console:\n"
458         << console_log_txt;
459   }
460 
461   if (test_params.consolelog_expected_level >= LogLevel::kWarning &&
462       test_params.consolelog_expected_level != LogLevel::kNotSet) {
463     EXPECT_THAT(console_log_txt, HasSubstr(kWarningLogEntry))
464         << "console:\n"
465         << console_log_txt;
466   } else {
467     EXPECT_THAT(console_log_txt, Not(HasSubstr(kWarningLogEntry)))
468         << "console:\n"
469         << console_log_txt;
470   }
471 
472   if (test_params.consolelog_expected_level >= LogLevel::kSystem &&
473       test_params.consolelog_expected_level != LogLevel::kNotSet) {
474     // No SYSTEM output from Router today, so disable until Router does
475 #if 0
476     EXPECT_THAT(console_log_txt, HasSubstr(kSystemLogEntry)) << "console:\n"
477                                                              << console_log_txt;
478 #endif
479   } else {
480     // No SYSTEM output from Router today, so disable until Router does
481 #if 0
482     EXPECT_THAT(console_log_txt, Not(HasSubstr(kSystemLogEntry)))
483         << "console:\n"
484         << console_log_txt;
485 #endif
486   }
487 
488   // check the file log if it contains what's expected
489   const std::string file_log_txt =
490       router.get_full_logfile("mysqlrouter.log", tmp_dir.name());
491 
492   if (test_params.filelog_expected_level >= LogLevel::kDebug &&
493       test_params.filelog_expected_level != LogLevel::kNotSet) {
494     EXPECT_THAT(file_log_txt, HasSubstr(kDebugLogEntry))
495         << "file:\n"
496         << file_log_txt << "\nconsole:\n"
497         << console_log_txt;
498   } else {
499     EXPECT_THAT(file_log_txt, Not(HasSubstr(kDebugLogEntry)))
500         << "file:\n"
501         << file_log_txt << "\nconsole:\n"
502         << console_log_txt;
503   }
504 
505   if (test_params.filelog_expected_level >= LogLevel::kNote &&
506       test_params.filelog_expected_level != LogLevel::kNotSet) {
507     // No NOTE output from Router today, so check that we see info
508     EXPECT_THAT(file_log_txt, HasSubstr(kInfoLogEntry))
509         << "file:\n"
510         << file_log_txt << "\nconsole:\n"
511         << console_log_txt;
512   } else {
513     // No NOTE output from Router today, so disable until Router does
514 #if 0
515     EXPECT_THAT(file_log_txt, Not(HasSubstr(kNoteLogEntry)))
516         << "file:\n"
517         << file_log_txt << "\nconsole:\n"
518         << console_log_txt;
519 #endif
520   }
521 
522   if (test_params.filelog_expected_level >= LogLevel::kInfo &&
523       test_params.filelog_expected_level != LogLevel::kNotSet) {
524     EXPECT_THAT(file_log_txt, HasSubstr(kInfoLogEntry))
525         << "file:\n"
526         << file_log_txt << "\nconsole:\n"
527         << console_log_txt;
528   } else {
529     EXPECT_THAT(file_log_txt, Not(HasSubstr(kInfoLogEntry)))
530         << "file:\n"
531         << file_log_txt << "\nconsole:\n"
532         << console_log_txt;
533   }
534 
535   if (test_params.filelog_expected_level >= LogLevel::kWarning &&
536       test_params.filelog_expected_level != LogLevel::kNotSet) {
537     EXPECT_THAT(file_log_txt, HasSubstr(kWarningLogEntry))
538         << "file:\n"
539         << file_log_txt << "\nconsole:\n"
540         << console_log_txt;
541   } else {
542     EXPECT_THAT(file_log_txt, Not(HasSubstr(kWarningLogEntry)))
543         << "file:\n"
544         << file_log_txt << "\nconsole:\n"
545         << console_log_txt;
546   }
547 
548   if (test_params.filelog_expected_level >= LogLevel::kSystem &&
549       test_params.filelog_expected_level != LogLevel::kNotSet) {
550     // No SYSTEM output from Router today, so disable until Router does
551 #if 0
552     EXPECT_THAT(file_log_txt, HasSubstr(kSystemLogEntry))
553         << "file:\n"
554         << file_log_txt << "\nconsole:\n"
555         << console_log_txt;
556 #endif
557   } else {
558     // No SYSTEM output from Router today, so disable until Router does
559 #if 0
560     EXPECT_THAT(file_log_txt, Not(HasSubstr(kSystemLogEntry)))
561         << "file:\n"
562         << file_log_txt << "\nconsole:\n"
563         << console_log_txt;
564 #endif
565   }
566 }
567 
568 INSTANTIATE_TEST_SUITE_P(
569     LoggingConfigTest, RouterLoggingTestConfig,
570     ::testing::Values(
571         // no logger section, no sinks sections
572         // logging_folder not empty so we are expected to log to the file
573         // with a warning level so info and debug logs will not be there
574         /*0*/ LoggingConfigOkParams(
575             "",
576             /* logging_folder_empty = */ false,
577             /* consolelog_expected_level =  */ LogLevel::kNotSet,
578             /* filelog_expected_level =  */ LogLevel::kWarning),
579 
580         // no logger section, no sinks sections
581         // logging_folder empty so we are expected to log to the console
582         // with a warning level so info and debug logs will not be there
583         /*1*/
584         LoggingConfigOkParams(
585             "",
586             /* logging_folder_empty = */ true,
587             /* consolelog_expected_level =  */ LogLevel::kWarning,
588             /* filelog_expected_level =  */ LogLevel::kNotSet),
589 
590         // logger section, no sinks sections
591         // logging_folder not empty so we are expected to log to the file
592         // with a warning level as level is not redefined in the [logger]
593         // section
594         /*2*/
595         LoggingConfigOkParams(
596             "[logger]",
597             /* logging_folder_empty = */ false,
598             /* consolelog_expected_level =  */ LogLevel::kNotSet,
599             /* filelog_expected_level =  */ LogLevel::kWarning),
600 
601         // logger section, no sinks sections
602         // logging_folder not empty so we are expected to log to the file
603         // with a level defined in the logger section
604         /*3*/
605         LoggingConfigOkParams(
606             "[logger]\n"
607             "level=info\n",
608             /* logging_folder_empty = */ false,
609             /* consolelog_expected_level =  */ LogLevel::kNotSet,
610             /* filelog_expected_level =  */ LogLevel::kInfo),
611 
612         // logger section, no sinks sections; logging_folder is empty so we are
613         // expected to log to the console with a level defined in the logger
614         // section
615         /*4*/
616         LoggingConfigOkParams(
617             "[logger]\n"
618             "level=info\n",
619             /* logging_folder_empty = */ true,
620             /* consolelog_expected_level =  */ LogLevel::kInfo,
621             /* filelog_expected_level =  */ LogLevel::kNotSet),
622 
623         // consolelog configured as a sink; it does not have its section in the
624         // config but that is not an error; even though the logging folder is
625         // not empty, we still don't log to the file as sinks= setting wants use
626         // the console
627         /*5*/
628         LoggingConfigOkParams(
629             "[logger]\n"
630             "level=debug\n"
631             "sinks=consolelog\n",
632             /* logging_folder_empty = */ false,
633             /* consolelog_expected_level =  */ LogLevel::kDebug,
634             /* filelog_expected_level =  */ LogLevel::kNotSet),
635 
636         // 2 sinks have sections but consolelog is not defined as a sink in the
637         // [logger] section so there should be no logging to the console (after
638         // [logger] is initialised; prior to that all is logged to the console
639         // by default)
640         /*6*/
641         LoggingConfigOkParams(
642             "[logger]\n"
643             "sinks=filelog\n"
644             "level=debug\n"
645             "[filelog]\n"
646             "[consolelog]\n"
647             "level=debug\n",
648             /* logging_folder_empty = */ false,
649             /* consolelog_expected_level =  */ LogLevel::kNotSet,
650             /* filelog_expected_level =  */ LogLevel::kDebug),
651 
652         // 2 sinks, both should inherit log level from [logger] section (which
653         // is debug)
654         /*7*/
655         LoggingConfigOkParams(
656             "[logger]\n"
657             "sinks=filelog,consolelog\n"
658             "level=debug\n"
659             "[filelog]\n"
660             "[consolelog]\n",
661             /* logging_folder_empty = */ false,
662             /* consolelog_expected_level =  */ LogLevel::kDebug,
663             /* filelog_expected_level =  */ LogLevel::kDebug),
664 
665         // 2 sinks, both should inherit log level from [logger] section (which
666         // is info); debug logs are not expected for both sinks
667         /*8*/
668         LoggingConfigOkParams(
669             "[logger]\n"
670             "sinks=filelog,consolelog\n"
671             "level=info\n"
672             "[filelog]\n"
673             "[consolelog]\n",
674             /* logging_folder_empty = */ false,
675             /* consolelog_expected_level =  */ LogLevel::kInfo,
676             /* filelog_expected_level =  */ LogLevel::kInfo),
677 
678         // 2 sinks, both should inherit log level from [logger] section (which
679         // is warning); neither debug not info logs are not expected for both
680         // sinks
681         /*9*/
682         LoggingConfigOkParams(
683             "[logger]\n"
684             "sinks=filelog,consolelog\n"
685             "level=warning\n"
686             "[filelog]\n"
687             "[consolelog]\n",
688             /* logging_folder_empty = */ false,
689             /* consolelog_expected_level =  */ LogLevel::kWarning,
690             /* filelog_expected_level =  */ LogLevel::kWarning),
691 
692         // 2 sinks, one overwrites the default log level, the other inherits
693         // default from [logger] section
694         /*10*/
695         LoggingConfigOkParams(
696             "[logger]\n"
697             "sinks=filelog,consolelog\n"
698             "level=info\n"
699             "[filelog]\n"
700             "level=debug\n"
701             "[consolelog]\n",
702             /* logging_folder_empty = */ false,
703             /* consolelog_expected_level =  */ LogLevel::kInfo,
704             /* filelog_expected_level =  */ LogLevel::kDebug),
705 
706         // 2 sinks, each defines its own custom log level that overwrites the
707         // default from [logger] section
708         /*11*/
709         LoggingConfigOkParams(
710             "[logger]\n"
711             "sinks=filelog,consolelog\n"
712             "level=info\n"
713             "[filelog]\n"
714             "level=debug\n"
715             "[consolelog]\n"
716             "level=warning\n",
717             /* logging_folder_empty = */ false,
718             /* consolelog_expected_level =  */ LogLevel::kWarning,
719             /* filelog_expected_level =  */ LogLevel::kDebug),
720 
721         // 2 sinks, each defines its own custom log level that overwrites the
722         // default from [logger] section
723         /*12*/
724         LoggingConfigOkParams(
725             "[logger]\n"
726             "sinks=filelog,consolelog\n"
727             "level=warning\n"
728             "[filelog]\n"
729             "level=info\n"
730             "[consolelog]\n"
731             "level=warning\n",
732             /* logging_folder_empty = */ false,
733             /* consolelog_expected_level =  */ LogLevel::kWarning,
734             /* filelog_expected_level =  */ LogLevel::kInfo),
735 
736         // 2 sinks, each defines its own custom log level (that is more strict)
737         // that overwrites the default from [logger] section
738         /*13*/
739         LoggingConfigOkParams(
740             "[logger]\n"
741             "sinks=filelog,consolelog\n"
742             "level=debug\n"
743             "[filelog]\n"
744             "level=info\n"
745             "[consolelog]\n"
746             "level=warning\n",
747             /* logging_folder_empty = */ false,
748             /* consolelog_expected_level =  */ LogLevel::kWarning,
749             /* filelog_expected_level =  */ LogLevel::kInfo),
750 
751         // 2 sinks,no level in the [logger] section and no level in the sinks
752         // sections; default log level should be used (which is warning)
753         /*14*/
754         LoggingConfigOkParams(
755             "[logger]\n"
756             "sinks=filelog,consolelog\n"
757             "[filelog]\n"
758             "[consolelog]\n",
759             /* logging_folder_empty = */ false,
760             /* consolelog_expected_level =  */ LogLevel::kWarning,
761             /* filelog_expected_level =  */ LogLevel::kWarning),
762 
763         // 2 sinks, level in the [logger] section is warning; it should be
764         // used by the sinks as they don't redefine it in their sections
765         /*15*/
766         LoggingConfigOkParams(
767             "[logger]\n"
768             "level=warning\n"
769             "sinks=filelog,consolelog\n",
770             /* logging_folder_empty = */ false,
771             /* consolelog_expected_level =  */ LogLevel::kWarning,
772             /* filelog_expected_level =  */ LogLevel::kWarning),
773 
774         // 2 sinks, level in the [logger] section is error; it should be used
775         // by the sinks as they don't redefine it in their sections
776         /*16*/
777         LoggingConfigOkParams(
778             "[logger]\n"
779             "level=error\n"
780             "sinks=filelog,consolelog\n",
781             /* logging_folder_empty = */ false,
782             /* consolelog_expected_level =  */ LogLevel::kError,
783             /* filelog_expected_level =  */ LogLevel::kError),
784 
785         // 2 sinks, no level in the [logger] section, each defines it's own
786         // level
787         /*17*/
788         LoggingConfigOkParams(
789             "[logger]\n"
790             "sinks=filelog,consolelog\n"
791             "[filelog]\n"
792             "level=error\n"
793             "[consolelog]\n"
794             "level=debug\n",
795             /* logging_folder_empty = */ false,
796             /* consolelog_expected_level =  */ LogLevel::kDebug,
797             /* filelog_expected_level =  */ LogLevel::kError),
798 
799         // 2 sinks, no level in the [logger] section, one defines it's own
800         // level, the other expected to go with default (warning)
801         /*18*/
802         LoggingConfigOkParams(
803             "[logger]\n"
804             "sinks=filelog,consolelog\n"
805             "[filelog]\n"
806             "level=error\n",
807             /* logging_folder_empty = */ false,
808             /* consolelog_expected_level =  */ LogLevel::kWarning,
809             /* filelog_expected_level =  */ LogLevel::kError),
810         // level note to filelog sink (TS_FR1_01)
811         // Note: Router does not log at NOTE now
812         /*19*/
813         LoggingConfigOkParams(
814             "[logger]\n"
815             "level=note\n"
816             "sinks=filelog\n",
817             /* logging_folder_empty = */ false,
818             /* consolelog_expected_level =  */ LogLevel::kNotSet,
819             /* filelog_expected_level =  */ LogLevel::kNote),
820         // note level to filelog sink (TS_FR1_02)
821         // Note: Router does not log at NOTE now
822         /*20*/
823         LoggingConfigOkParams(
824             "[logger]\n"
825             "level=system\n"
826             "sinks=filelog\n",
827             /* logging_folder_empty = */ false,
828             /* consolelog_expected_level =  */ LogLevel::kNotSet,
829             /* filelog_expected_level =  */ LogLevel::kSystem)));
830 
831 #ifndef WIN32
832 INSTANTIATE_TEST_SUITE_P(
833     LoggingConfigTestUnix, RouterLoggingTestConfig,
834     ::testing::Values(
835         // We can't reliably check if the syslog logging is working with a
836         // component test as this is too operating system intrusive and we are
837         // supposed to run on pb2 environment. Let's at least check that this
838         // sink type is supported
839         // Level note to syslog,filelog (TS_FR1_06)
840         /*0*/
841         LoggingConfigOkParams(
842             "[logger]\n"
843             "level=note\n"
844             "sinks=syslog,filelog\n",
845             /* logging_folder_empty = */ false,
846             /* consolelog_expected_level =  */ LogLevel::kNotSet,
847             /* filelog_expected_level =  */ LogLevel::kNote),
848         // Level system to syslog,filelog (TS_FR1_07)
849         /*1*/
850         LoggingConfigOkParams(
851             "[logger]\n"
852             "level=system\n"
853             "sinks=syslog,filelog\n",
854             /* logging_folder_empty = */ false,
855             /* consolelog_expected_level =  */ LogLevel::kNotSet,
856             /* filelog_expected_level =  */ LogLevel::kSystem),
857         // All sinks (TS_FR1_08)
858         /*2*/
859         LoggingConfigOkParams(
860             "[logger]\n"
861             "level=debug\n"
862             "sinks=syslog,filelog,consolelog\n"
863             "[consolelog]\n"
864             "level=note\n"
865             "[syslog]\n"
866             "level=system\n",
867             /* logging_folder_empty = */ false,
868             /* consolelog_expected_level =  */ LogLevel::kNote,
869             /* filelog_expected_level =  */ LogLevel::kDebug),
870         // Verify filename option is disregarded by syslog sink
871         /*3*/
872         LoggingConfigOkParams(
873             "[logger]\n"
874             "level=note\n"
875             "sinks=syslog,filelog\n"
876             "[syslog]\n"
877             "filename=foo.log",
878             /* logging_folder_empty = */ false,
879             /* consolelog_expected_level =  */ LogLevel::kNotSet,
880             /* filelog_expected_level =  */ LogLevel::kNote)));
881 #else
882 INSTANTIATE_TEST_SUITE_P(
883     LoggingConfigTestWindows, RouterLoggingTestConfig,
884     ::testing::Values(
885         // We can't reliably check if the eventlog logging is working with a
886         // component test as this is too operating system intrusive and also
887         // requires admin priviledges to setup and we are supposed to run on pb2
888         // environment. Let's at least check that this sink type is supported.
889         // Level note to eventlog,filelog (TS_FR1_03)
890         /*0*/
891         LoggingConfigOkParams(
892             "[logger]\n"
893             "level=note\n"
894             "sinks=eventlog,filelog\n",
895             /* logging_folder_empty = */ false,
896             /* consolelog_expected_level =  */ LogLevel::kNotSet,
897             /* filelog_expected_level =  */ LogLevel::kNote),
898         // Level system to eventlog,filelog (TS_FR1_04)
899         /*1*/
900         LoggingConfigOkParams(
901             "[logger]\n"
902             "level=system\n"
903             "sinks=eventlog,filelog\n",
904             /* logging_folder_empty = */ false,
905             /* consolelog_expected_level =  */ LogLevel::kNotSet,
906             /* filelog_expected_level =  */ LogLevel::kSystem),
907         // All sinks with note and system included (TS_FR1_05)
908         /*2*/
909         LoggingConfigOkParams(
910             "[logger]\n"
911             "level=debug\n"
912             "sinks=eventlog,filelog,consolelog\n"
913             "[consolelog]\n"
914             "level=note\n"
915             "[eventlog]\n"
916             "level=system\n",
917             /* logging_folder_empty = */ false,
918             /* consolelog_expected_level =  */ LogLevel::kNote,
919             /* filelog_expected_level =  */ LogLevel::kDebug),
920         // Verify filename option is disregarded by eventlog sink
921         /*3*/
922         LoggingConfigOkParams(
923             "[logger]\n"
924             "level=system\n"
925             "sinks=eventlog,filelog\n"
926             "[eventlog]\n"
927             "filename=foo.log",
928             /* logging_folder_empty = */ false,
929             /* consolelog_expected_level =  */ LogLevel::kNotSet,
930             /* filelog_expected_level =  */ LogLevel::kSystem)));
931 #endif
932 
933 /**************************************************/
934 /* Tests for logger configuration errors          */
935 /**************************************************/
936 
937 struct LoggingConfigErrorParams {
938   std::string logger_config;
939   bool logging_folder_empty;
940 
941   std::string expected_error;
942 
LoggingConfigErrorParamsLoggingConfigErrorParams943   LoggingConfigErrorParams(const std::string &logger_config_,
944                            const bool logging_folder_empty_,
945                            const std::string &expected_error_)
946       : logger_config(logger_config_),
947         logging_folder_empty(logging_folder_empty_),
948         expected_error(expected_error_) {}
949 };
950 
operator <<(::std::ostream & os,const LoggingConfigErrorParams & ltp)951 ::std::ostream &operator<<(::std::ostream &os,
952                            const LoggingConfigErrorParams &ltp) {
953   return os << "config=" << ltp.logger_config
954             << ", logging_folder_empty=" << ltp.logging_folder_empty;
955 }
956 
957 class RouterLoggingConfigError
958     : public RouterLoggingTest,
959       public ::testing::WithParamInterface<LoggingConfigErrorParams> {};
960 
961 /** @test This test verifies that a proper error gets printed on the console for
962  * a particular logging configuration
963  */
TEST_P(RouterLoggingConfigError,LoggingConfigError)964 TEST_P(RouterLoggingConfigError, LoggingConfigError) {
965   auto test_params = GetParam();
966 
967   TempDirectory tmp_dir;
968   auto conf_params = get_DEFAULT_defaults();
969   conf_params["logging_folder"] =
970       test_params.logging_folder_empty ? "" : tmp_dir.name();
971 
972   TempDirectory conf_dir("conf");
973   const std::string conf_text = "[keepalive]\n" + test_params.logger_config;
974 
975   const std::string conf_file =
976       create_config_file(conf_dir.name(), conf_text, &conf_params);
977 
978   auto &router = launch_router({"-c", conf_file}, 1);
979 
980   check_exit_code(router, EXIT_FAILURE);
981 
982   // the error happens during the logger initialization so we expect the message
983   // on the console which is the default sink until we switch to the
984   // configuration from the config file
985   const std::string console_log_txt = router.get_full_output();
986 
987   EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
988       << "\nconsole:\n"
989       << console_log_txt;
990 }
991 
992 INSTANTIATE_TEST_SUITE_P(
993     LoggingConfigError, RouterLoggingConfigError,
994     ::testing::Values(
995         // Unknown sink name in the [logger] section
996         /*0*/ LoggingConfigErrorParams(
997             "[logger]\n"
998             "sinks=unknown\n"
999             "level=debug\n",
1000             /* logging_folder_empty = */ false,
1001             /* expected_error =  */
1002             "Configuration error: Unsupported logger sink type: 'unknown'"),
1003 
1004         // Empty sinks option
1005         /*1*/
1006         LoggingConfigErrorParams(
1007             "[logger]\n"
1008             "sinks=\n",
1009             /* logging_folder_empty = */ false,
1010             /* expected_error =  */
1011             "plugin 'logger' init failed: sinks option does not contain any "
1012             "valid sink name, was ''"),
1013 
1014         // Empty sinks list
1015         /*2*/
1016         LoggingConfigErrorParams(
1017             "[logger]\n"
1018             "sinks=,\n",
1019             /* logging_folder_empty = */ false,
1020             /* expected_error =  */
1021             "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1022 
1023         // Leading comma on a sinks list
1024         /*3*/
1025         LoggingConfigErrorParams(
1026             "[logger]\n"
1027             "sinks=,consolelog\n",
1028             /* logging_folder_empty = */ false,
1029             /* expected_error =  */
1030             "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1031 
1032         // Terminating comma on a sinks list
1033         /*4*/
1034         LoggingConfigErrorParams(
1035             "[logger]\n"
1036             "sinks=consolelog,\n",
1037             /* logging_folder_empty = */ false,
1038             /* expected_error =  */
1039             "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1040 
1041         // Two commas separating sinks
1042         /*5*/
1043         LoggingConfigErrorParams(
1044             "[logger]\n"
1045             "sinks=consolelog,,filelog\n",
1046             /* logging_folder_empty = */ false,
1047             /* expected_error =  */
1048             "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1049 
1050         // Empty space as a sink name
1051         /*6*/
1052         LoggingConfigErrorParams(
1053             "[logger]\n"
1054             "sinks= \n",
1055             /* logging_folder_empty = */ false,
1056             /* expected_error =  */
1057             "plugin 'logger' init failed: sinks option does not contain any "
1058             "valid sink name, was ''"),
1059 
1060         // Invalid log level in the [logger] section
1061         /*7*/
1062         LoggingConfigErrorParams(
1063             "[logger]\n"
1064             "sinks=consolelog\n"
1065             "level=invalid\n"
1066             "[consolelog]\n",
1067             /* logging_folder_empty = */ false,
1068             /* expected_error =  */
1069             "Configuration error: Log level 'invalid' is not valid. Valid "
1070             "values are: debug, error, fatal, info, note, system, and warning"),
1071 
1072         // Invalid log level in the sink section
1073         /*8*/
1074         LoggingConfigErrorParams(
1075             "[logger]\n"
1076             "sinks=consolelog\n"
1077             "[consolelog]\n"
1078             "level=invalid\n",
1079             /* logging_folder_empty = */ false,
1080             /* expected_error =  */
1081             "Configuration error: Log level 'invalid' is not valid. Valid "
1082             "values are: debug, error, fatal, info, note, system, and warning"),
1083 
1084         // Both level and sinks valuse invalid in the [logger] section
1085         /*9*/
1086         LoggingConfigErrorParams(
1087             "[logger]\n"
1088             "sinks=invalid\n"
1089             "level=invalid\n"
1090             "[consolelog]\n",
1091             /* logging_folder_empty = */ false,
1092             /* expected_error =  */
1093             "Configuration error: Log level 'invalid' is not valid. Valid "
1094             "values are: debug, error, fatal, info, note, system, and warning"),
1095 
1096         // Logging folder is empty but we request filelog as sink
1097         /*10*/
1098         LoggingConfigErrorParams(
1099             "[logger]\n"
1100             "sinks=filelog\n",
1101             /* logging_folder_empty = */ true,
1102             /* expected_error =  */
1103             "plugin 'logger' init failed: filelog sink configured but the "
1104             "logging_folder is empty")));
1105 
1106 #ifndef _WIN32
1107 INSTANTIATE_TEST_SUITE_P(
1108     LoggingConfigErrorUnix, RouterLoggingConfigError,
1109     ::testing::Values(
1110         // We can't reliably check if the syslog logging is working with a
1111         // component test as this is too operating system intrusive and we are
1112         // supposed to run on pb2 environment. Let's at least check that this
1113         // sink type is supported
1114         LoggingConfigErrorParams(
1115             "[logger]\n"
1116             "sinks=syslog\n"
1117             "[syslog]\n"
1118             "level=invalid\n",
1119             /* logging_folder_empty = */ false,
1120             /* expected_error =  */
1121             "Configuration error: Log level 'invalid' is not valid. Valid "
1122             "values are: debug, error, fatal, info, note, system, and warning"),
1123 
1124         // Let's also check that the eventlog is NOT supported
1125         LoggingConfigErrorParams(
1126             "[logger]\n"
1127             "sinks=eventlog\n"
1128             "[eventlog]\n"
1129             "level=invalid\n",
1130             /* logging_folder_empty = */ false,
1131             /* expected_error =  */
1132             "Loading plugin for config-section '[eventlog]' failed")));
1133 #else
1134 INSTANTIATE_TEST_SUITE_P(
1135     LoggingConfigErrorWindows, RouterLoggingConfigError,
1136     ::testing::Values(
1137         // We can't reliably check if the eventlog logging is working with a
1138         // component test as this is too operating system intrusive and also
1139         // requires admin priviledges to setup and we are supposed to run on pb2
1140         // environment. Let's at least check that this sink type is supported
1141         LoggingConfigErrorParams(
1142             "[logger]\n"
1143             "sinks=eventlog\n"
1144             "[eventlog]\n"
1145             "level=invalid\n",
1146             /* logging_folder_empty = */ false,
1147             /* expected_error =  */
1148             "Configuration error: Log level 'invalid' is not valid. Valid "
1149             "values are: debug, error, fatal, info, note, system, and warning"),
1150 
1151         // Let's also check that the syslog is NOT supported
1152         LoggingConfigErrorParams(
1153             "[logger]\n"
1154             "sinks=syslog\n"
1155             "[syslog]\n"
1156             "level=invalid\n",
1157             /* logging_folder_empty = */ false,
1158             /* expected_error =  */
1159             "Loading plugin for config-section '[syslog]' failed")));
1160 #endif
1161 
1162 class RouterLoggingTestTimestampPrecisionConfig
1163     : public RouterLoggingTest,
1164       public ::testing::WithParamInterface<LoggingConfigOkParams> {};
1165 
1166 #define DATE_REGEX "[0-9]{4}-[0-9]{2}-[0-9]{2}"
1167 #define TIME_REGEX "[0-9]{2}:[0-9]{2}:[0-9]{2}"
1168 #define TS_MSEC_REGEX ".[0-9]{3}"
1169 #define TS_USEC_REGEX ".[0-9]{6}"
1170 #define TS_NSEC_REGEX ".[0-9]{9}"
1171 #define TS_REGEX DATE_REGEX " " TIME_REGEX
1172 
1173 const std::string kTimestampSecRegex = TS_REGEX " ";
1174 const std::string kTimestampMillisecRegex = TS_REGEX TS_MSEC_REGEX " ";
1175 const std::string kTimestampMicrosecRegex = TS_REGEX TS_USEC_REGEX " ";
1176 const std::string kTimestampNanosecRegex = TS_REGEX TS_NSEC_REGEX " ";
1177 
1178 /** @test This test verifies that a proper loggs are written to selected sinks
1179  * for various sinks/levels combinations.
1180  */
TEST_P(RouterLoggingTestTimestampPrecisionConfig,LoggingTestTimestampPrecisionConfig)1181 TEST_P(RouterLoggingTestTimestampPrecisionConfig,
1182        LoggingTestTimestampPrecisionConfig) {
1183   auto test_params = GetParam();
1184 
1185   TempDirectory tmp_dir;
1186   TcpPortPool port_pool;
1187   const auto router_port = port_pool.get_next_available();
1188   const auto server_port = port_pool.get_next_available();
1189 
1190   // Different log entries that are expected for different levels, but we only
1191   // care that something is logged, not what, when checking timestamps.
1192 
1193   // to trigger the warning entry in the log
1194   const std::string kRoutingConfig =
1195       "[routing]\n"
1196       "bind_address=127.0.0.1:" +
1197       std::to_string(router_port) +
1198       "\n"
1199       "destinations=localhost:" +
1200       std::to_string(server_port) +
1201       "\n"
1202       "routing_strategy=round-robin\n";
1203 
1204   auto conf_params = get_DEFAULT_defaults();
1205   conf_params["logging_folder"] =
1206       test_params.logging_folder_empty ? "" : tmp_dir.name();
1207 
1208   TempDirectory conf_dir("conf");
1209   const std::string conf_text =
1210       test_params.logger_config + "\n" + kRoutingConfig;
1211 
1212   const std::string conf_file =
1213       create_config_file(conf_dir.name(), conf_text, &conf_params);
1214 
1215   auto &router = launch_router({"-c", conf_file});
1216 
1217   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
1218 
1219   // try to make a connection; this will fail but should generate a warning in
1220   // the logs
1221   mysqlrouter::MySQLSession client;
1222   try {
1223     client.connect("127.0.0.1", router_port, "username", "password", "", "");
1224   } catch (const std::exception &exc) {
1225     if (std::string(exc.what()).find("Error connecting to MySQL server") !=
1226         std::string::npos) {
1227       // that's what we expect
1228     } else
1229       throw;
1230   }
1231 
1232   // check the console log if it contains what's expected
1233   std::string console_log_txt = router.get_full_output();
1234 
1235   // strip first line before checking if needed
1236   const std::string prefix = "logging facility initialized";
1237   if (std::mismatch(console_log_txt.begin(), console_log_txt.end(),
1238                     prefix.begin(), prefix.end())
1239           .second == prefix.end()) {
1240     console_log_txt.erase(0, console_log_txt.find("\n") + 1);
1241   }
1242 
1243   if (test_params.consolelog_expected_level != LogLevel::kNotSet) {
1244     switch (test_params.consolelog_expected_timestamp_precision) {
1245       case LogTimestampPrecision::kNotSet:
1246       case LogTimestampPrecision::kSec:
1247         // EXPECT 12:00:00
1248         EXPECT_TRUE(pattern_found(console_log_txt, kTimestampSecRegex))
1249             << console_log_txt;
1250         break;
1251       case LogTimestampPrecision::kMilliSec:
1252         // EXPECT 12:00:00.000
1253         EXPECT_TRUE(pattern_found(console_log_txt, kTimestampMillisecRegex))
1254             << console_log_txt;
1255         break;
1256       case LogTimestampPrecision::kMicroSec:
1257         // EXPECT 12:00:00.000000
1258         EXPECT_TRUE(pattern_found(console_log_txt, kTimestampMicrosecRegex))
1259             << console_log_txt;
1260         break;
1261       case LogTimestampPrecision::kNanoSec:
1262         // EXPECT 12:00:00.000000000
1263         EXPECT_TRUE(pattern_found(console_log_txt, kTimestampNanosecRegex))
1264             << console_log_txt;
1265         break;
1266     }
1267   }
1268 
1269   // check the file log if it contains what's expected
1270   std::string file_log_txt =
1271       router.get_full_logfile("mysqlrouter.log", tmp_dir.name());
1272 
1273   // strip first line before checking if needed
1274   if (std::mismatch(file_log_txt.begin(), file_log_txt.end(), prefix.begin(),
1275                     prefix.end())
1276           .second == prefix.end()) {
1277     file_log_txt.erase(0, file_log_txt.find("\n") + 1);
1278   }
1279 
1280   if (test_params.filelog_expected_level != LogLevel::kNotSet) {
1281     switch (test_params.filelog_expected_timestamp_precision) {
1282       case LogTimestampPrecision::kNotSet:
1283       case LogTimestampPrecision::kSec:
1284         // EXPECT 12:00:00
1285         EXPECT_TRUE(pattern_found(file_log_txt, kTimestampSecRegex))
1286             << file_log_txt;
1287         break;
1288       case LogTimestampPrecision::kMilliSec:
1289         // EXPECT 12:00:00.000
1290         EXPECT_TRUE(pattern_found(file_log_txt, kTimestampMillisecRegex))
1291             << file_log_txt;
1292         break;
1293       case LogTimestampPrecision::kMicroSec:
1294         // EXPECT 12:00:00.000000
1295         EXPECT_TRUE(pattern_found(file_log_txt, kTimestampMicrosecRegex))
1296             << file_log_txt;
1297         break;
1298       case LogTimestampPrecision::kNanoSec:
1299         // EXPECT 12:00:00.000000000
1300         EXPECT_TRUE(pattern_found(file_log_txt, kTimestampNanosecRegex))
1301             << file_log_txt;
1302         break;
1303     }
1304   }
1305 }
1306 
1307 #define TS_FR1_1_STR(x)        \
1308   "[logger]\n"                 \
1309   "level=debug\n"              \
1310   "sinks=consolelog,filelog\n" \
1311   "timestamp_precision=" x     \
1312   "\n"                         \
1313   "[consolelog]\n\n[filelog]\n\n"
1314 
1315 #define TS_FR1_2_STR(x) TS_FR1_1_STR(x)
1316 
1317 #define TS_FR1_3_STR(x) TS_FR1_1_STR(x)
1318 
1319 INSTANTIATE_TEST_SUITE_P(
1320     LoggingConfigTimestampPrecisionTest,
1321     RouterLoggingTestTimestampPrecisionConfig,
1322     ::testing::Values(
1323         // no logger section, no sinks sections
1324         // logging_folder not empty so we are expected to log to the file
1325         // with a warning level so info and debug logs will not be there
1326         /*0*/ LoggingConfigOkParams(
1327             "",
1328             /* logging_folder_empty = */ false,
1329             /* consolelog_expected_level =  */ LogLevel::kNotSet,
1330             /* filelog_expected_level =  */ LogLevel::kWarning,
1331             /* consolelog_expected_timestamp_precision = */
1332             LogTimestampPrecision::kNotSet,
1333             /* filelog_expected_timestamp_precision = */
1334             LogTimestampPrecision::kNotSet),
1335         // Two sinks, common timestamp_precision
1336         /*** TS_FR1_1 ***/
1337         /*1*/ /*TS_FR1_1.1*/
1338         LoggingConfigOkParams(
1339             TS_FR1_1_STR("second"),
1340             /* logging_folder_empty = */ false,
1341             /* consolelog_expected_level =  */ LogLevel::kDebug,
1342             /* filelog_expected_level =  */ LogLevel::kDebug,
1343             /* consolelog_expected_timestamp_precision = */
1344             LogTimestampPrecision::kSec,
1345             /* filelog_expected_timestamp_precision = */
1346             LogTimestampPrecision::kSec),
1347         /*2*/ /*TS_FR1_1.2*/
1348         LoggingConfigOkParams(
1349             TS_FR1_1_STR("Second"),
1350             /* logging_folder_empty = */ false,
1351             /* consolelog_expected_level =  */ LogLevel::kDebug,
1352             /* filelog_expected_level =  */ LogLevel::kDebug,
1353             /* consolelog_expected_timestamp_precision = */
1354             LogTimestampPrecision::kSec,
1355             /* filelog_expected_timestamp_precision = */
1356             LogTimestampPrecision::kSec),
1357         /*3*/ /*TS_FR1_1.3*/
1358         LoggingConfigOkParams(
1359             TS_FR1_1_STR("sec"),
1360             /* logging_folder_empty = */ false,
1361             /* consolelog_expected_level =  */ LogLevel::kDebug,
1362             /* filelog_expected_level =  */ LogLevel::kDebug,
1363             /* consolelog_expected_timestamp_precision = */
1364             LogTimestampPrecision::kSec,
1365             /* filelog_expected_timestamp_precision = */
1366             LogTimestampPrecision::kSec),
1367         /*4*/ /*TS_FR1_1.4*/
1368         LoggingConfigOkParams(
1369             TS_FR1_1_STR("SEC"),
1370             /* logging_folder_empty = */ false,
1371             /* consolelog_expected_level =  */ LogLevel::kDebug,
1372             /* filelog_expected_level =  */ LogLevel::kDebug,
1373             /* consolelog_expected_timestamp_precision = */
1374             LogTimestampPrecision::kSec,
1375             /* filelog_expected_timestamp_precision = */
1376             LogTimestampPrecision::kSec),
1377         /*5*/ /*TS_FR1_1.5*/
1378         LoggingConfigOkParams(
1379             TS_FR1_1_STR("s"),
1380             /* logging_folder_empty = */ false,
1381             /* consolelog_expected_level =  */ LogLevel::kDebug,
1382             /* filelog_expected_level =  */ LogLevel::kDebug,
1383             /* consolelog_expected_timestamp_precision = */
1384             LogTimestampPrecision::kSec,
1385             /* filelog_expected_timestamp_precision = */
1386             LogTimestampPrecision::kSec),
1387         /*6*/ /*TS_FR1_1.6*/
1388         LoggingConfigOkParams(
1389             TS_FR1_1_STR("S"),
1390             /* logging_folder_empty = */ false,
1391             /* consolelog_expected_level =  */ LogLevel::kDebug,
1392             /* filelog_expected_level =  */ LogLevel::kDebug,
1393             /* consolelog_expected_timestamp_precision = */
1394             LogTimestampPrecision::kSec,
1395             /* filelog_expected_timestamp_precision = */
1396             LogTimestampPrecision::kSec),
1397         /*** TS_FR1_2 ***/
1398         /*7*/ /*TS_FR1_2.1*/
1399         LoggingConfigOkParams(
1400             TS_FR1_2_STR("millisecond"),
1401             /* logging_folder_empty = */ false,
1402             /* consolelog_expected_level =  */ LogLevel::kDebug,
1403             /* filelog_expected_level =  */ LogLevel::kDebug,
1404             /* consolelog_expected_timestamp_precision = */
1405             LogTimestampPrecision::kMilliSec,
1406             /* filelog_expected_timestamp_precision = */
1407             LogTimestampPrecision::kMilliSec),
1408         /*8*/ /*TS_FR1_2.2*/
1409         LoggingConfigOkParams(
1410             TS_FR1_2_STR("MILLISECOND"),
1411             /* logging_folder_empty = */ false,
1412             /* consolelog_expected_level =  */ LogLevel::kDebug,
1413             /* filelog_expected_level =  */ LogLevel::kDebug,
1414             /* consolelog_expected_timestamp_precision = */
1415             LogTimestampPrecision::kMilliSec,
1416             /* filelog_expected_timestamp_precision = */
1417             LogTimestampPrecision::kMilliSec),
1418         /*9*/ /*TS_FR1_2.3*/
1419         LoggingConfigOkParams(
1420             TS_FR1_2_STR("msec"),
1421             /* logging_folder_empty = */ false,
1422             /* consolelog_expected_level =  */ LogLevel::kDebug,
1423             /* filelog_expected_level =  */ LogLevel::kDebug,
1424             /* consolelog_expected_timestamp_precision = */
1425             LogTimestampPrecision::kMilliSec,
1426             /* filelog_expected_timestamp_precision = */
1427             LogTimestampPrecision::kMilliSec),
1428         /*10*/ /*TS_FR1_2.4*/
1429         LoggingConfigOkParams(
1430             TS_FR1_2_STR("MSEC"),
1431             /* logging_folder_empty = */ false,
1432             /* consolelog_expected_level =  */ LogLevel::kDebug,
1433             /* filelog_expected_level =  */ LogLevel::kDebug,
1434             /* consolelog_expected_timestamp_precision = */
1435             LogTimestampPrecision::kMilliSec,
1436             /* filelog_expected_timestamp_precision = */
1437             LogTimestampPrecision::kMilliSec),
1438         /*11*/ /*TS_FR1_2.5*/
1439         LoggingConfigOkParams(
1440             TS_FR1_2_STR("ms"),
1441             /* logging_folder_empty = */ false,
1442             /* consolelog_expected_level =  */ LogLevel::kDebug,
1443             /* filelog_expected_level =  */ LogLevel::kDebug,
1444             /* consolelog_expected_timestamp_precision = */
1445             LogTimestampPrecision::kMilliSec,
1446             /* filelog_expected_timestamp_precision = */
1447             LogTimestampPrecision::kMilliSec),
1448         /*12*/ /*TS_FR1_2.6*/
1449         LoggingConfigOkParams(
1450             TS_FR1_2_STR("MS"),
1451             /* logging_folder_empty = */ false,
1452             /* consolelog_expected_level =  */ LogLevel::kDebug,
1453             /* filelog_expected_level =  */ LogLevel::kDebug,
1454             /* consolelog_expected_timestamp_precision = */
1455             LogTimestampPrecision::kMilliSec,
1456             /* filelog_expected_timestamp_precision = */
1457             LogTimestampPrecision::kMilliSec),
1458         /*** TS_FR1_3 ***/
1459         /*13*/ /*TS_FR1_3.1*/
1460         LoggingConfigOkParams(
1461             TS_FR1_3_STR("microsecond"),
1462             /* logging_folder_empty = */ false,
1463             /* consolelog_expected_level =  */ LogLevel::kDebug,
1464             /* filelog_expected_level =  */ LogLevel::kDebug,
1465             /* consolelog_expected_timestamp_precision = */
1466             LogTimestampPrecision::kMicroSec,
1467             /* filelog_expected_timestamp_precision = */
1468             LogTimestampPrecision::kMicroSec),
1469         /*14*/ /*TS_FR1_3.2*/
1470         LoggingConfigOkParams(
1471             TS_FR1_3_STR("Microsecond"),
1472             /* logging_folder_empty = */ false,
1473             /* consolelog_expected_level =  */ LogLevel::kDebug,
1474             /* filelog_expected_level =  */ LogLevel::kDebug,
1475             /* consolelog_expected_timestamp_precision = */
1476             LogTimestampPrecision::kMicroSec,
1477             /* filelog_expected_timestamp_precision = */
1478             LogTimestampPrecision::kMicroSec),
1479         /*15*/ /*TS_FR1_3.3*/
1480         LoggingConfigOkParams(
1481             TS_FR1_3_STR("usec"),
1482             /* logging_folder_empty = */ false,
1483             /* consolelog_expected_level =  */ LogLevel::kDebug,
1484             /* filelog_expected_level =  */ LogLevel::kDebug,
1485             /* consolelog_expected_timestamp_precision = */
1486             LogTimestampPrecision::kMicroSec,
1487             /* filelog_expected_timestamp_precision = */
1488             LogTimestampPrecision::kMicroSec),
1489         /*16*/ /*TS_FR1_3.4*/
1490         LoggingConfigOkParams(
1491             TS_FR1_3_STR("UsEC"),
1492             /* logging_folder_empty = */ false,
1493             /* consolelog_expected_level =  */ LogLevel::kDebug,
1494             /* filelog_expected_level =  */ LogLevel::kDebug,
1495             /* consolelog_expected_timestamp_precision = */
1496             LogTimestampPrecision::kMicroSec,
1497             /* filelog_expected_timestamp_precision = */
1498             LogTimestampPrecision::kMicroSec),
1499         /*17*/ /*TS_FR1_3.5*/
1500         LoggingConfigOkParams(
1501             TS_FR1_3_STR("us"),
1502             /* logging_folder_empty = */ false,
1503             /* consolelog_expected_level =  */ LogLevel::kDebug,
1504             /* filelog_expected_level =  */ LogLevel::kDebug,
1505             /* consolelog_expected_timestamp_precision = */
1506             LogTimestampPrecision::kMicroSec,
1507             /* filelog_expected_timestamp_precision = */
1508             LogTimestampPrecision::kMicroSec),
1509         /*18*/ /*TS_FR1_3.5*/
1510         LoggingConfigOkParams(
1511             TS_FR1_3_STR("US"),
1512             /* logging_folder_empty = */ false,
1513             /* consolelog_expected_level =  */ LogLevel::kDebug,
1514             /* filelog_expected_level =  */ LogLevel::kDebug,
1515             /* consolelog_expected_timestamp_precision = */
1516             LogTimestampPrecision::kMicroSec,
1517             /* filelog_expected_timestamp_precision = */
1518             LogTimestampPrecision::kMicroSec),
1519         /*** TS_FR1_4 ***/
1520         /*19*/ /*TS_FR1_4.1*/
1521         LoggingConfigOkParams(
1522             TS_FR1_3_STR("nanosecond"),
1523             /* logging_folder_empty = */ false,
1524             /* consolelog_expected_level =  */ LogLevel::kDebug,
1525             /* filelog_expected_level =  */ LogLevel::kDebug,
1526             /* consolelog_expected_timestamp_precision = */
1527             LogTimestampPrecision::kNanoSec,
1528             /* filelog_expected_timestamp_precision = */
1529             LogTimestampPrecision::kNanoSec),
1530         /*20*/ /*TS_FR1_4.2*/
1531         LoggingConfigOkParams(
1532             TS_FR1_3_STR("NANOSECOND"),
1533             /* logging_folder_empty = */ false,
1534             /* consolelog_expected_level =  */ LogLevel::kDebug,
1535             /* filelog_expected_level =  */ LogLevel::kDebug,
1536             /* consolelog_expected_timestamp_precision = */
1537             LogTimestampPrecision::kNanoSec,
1538             /* filelog_expected_timestamp_precision = */
1539             LogTimestampPrecision::kNanoSec),
1540         /*21*/ /*TS_FR1_4.3*/
1541         LoggingConfigOkParams(
1542             TS_FR1_3_STR("nsec"),
1543             /* logging_folder_empty = */ false,
1544             /* consolelog_expected_level =  */ LogLevel::kDebug,
1545             /* filelog_expected_level =  */ LogLevel::kDebug,
1546             /* consolelog_expected_timestamp_precision = */
1547             LogTimestampPrecision::kNanoSec,
1548             /* filelog_expected_timestamp_precision = */
1549             LogTimestampPrecision::kNanoSec),
1550         /*22*/ /*TS_FR1_4.4*/
1551         LoggingConfigOkParams(
1552             TS_FR1_3_STR("nSEC"),
1553             /* logging_folder_empty = */ false,
1554             /* consolelog_expected_level =  */ LogLevel::kDebug,
1555             /* filelog_expected_level =  */ LogLevel::kDebug,
1556             /* consolelog_expected_timestamp_precision = */
1557             LogTimestampPrecision::kNanoSec,
1558             /* filelog_expected_timestamp_precision = */
1559             LogTimestampPrecision::kNanoSec),
1560         /*23*/ /*TS_FR1_4.5*/
1561         LoggingConfigOkParams(
1562             TS_FR1_3_STR("ns"),
1563             /* logging_folder_empty = */ false,
1564             /* consolelog_expected_level =  */ LogLevel::kDebug,
1565             /* filelog_expected_level =  */ LogLevel::kDebug,
1566             /* consolelog_expected_timestamp_precision = */
1567             LogTimestampPrecision::kNanoSec,
1568             /* filelog_expected_timestamp_precision = */
1569             LogTimestampPrecision::kNanoSec),
1570         /*24*/ /*TS_FR1_4.6*/
1571         LoggingConfigOkParams(
1572             TS_FR1_3_STR("NS"),
1573             /* logging_folder_empty = */ false,
1574             /* consolelog_expected_level =  */ LogLevel::kDebug,
1575             /* filelog_expected_level =  */ LogLevel::kDebug,
1576             /* consolelog_expected_timestamp_precision = */
1577             LogTimestampPrecision::kNanoSec,
1578             /* filelog_expected_timestamp_precision = */
1579             LogTimestampPrecision::kNanoSec),
1580         /*25*/ /*TS_FR4_2*/
1581         LoggingConfigOkParams(
1582             "[logger]\n"
1583             "level=debug\n"
1584             "sinks=filelog\n"
1585             "[filelog]\n"
1586             "timestamp_precision=ms\n",
1587             /* logging_folder_empty = */ false,
1588             /* consolelog_expected_level =  */ LogLevel::kNotSet,
1589             /* filelog_expected_level =  */ LogLevel::kDebug,
1590             /* consolelog_expected_timestamp_precision = */
1591             LogTimestampPrecision::kNotSet,
1592             /* filelog_expected_timestamp_precision = */
1593             LogTimestampPrecision::kMilliSec),
1594         /*26*/ /*TS_FR4_3*/
1595         LoggingConfigOkParams(
1596             "[logger]\n"
1597             "level=debug\n"
1598             "sinks=filelog,consolelog\n"
1599             "[consolelog]\n"
1600             "timestamp_precision=ns\n",
1601             /* logging_folder_empty = */ false,
1602             /* consolelog_expected_level =  */ LogLevel::kDebug,
1603             /* filelog_expected_level =  */ LogLevel::kDebug,
1604             /* consolelog_expected_timestamp_precision = */
1605             LogTimestampPrecision::kNanoSec,
1606             /* filelog_expected_timestamp_precision = */
1607             LogTimestampPrecision::kSec)));
1608 
1609 INSTANTIATE_TEST_SUITE_P(
1610     LoggingConfigTimestampPrecisionError, RouterLoggingConfigError,
1611     ::testing::Values(
1612         // Unknown timestamp_precision value in a sink
1613         /*0*/ /*TS_FR3_1*/ LoggingConfigErrorParams(
1614             "[logger]\n"
1615             "sinks=consolelog\n"
1616             "[consolelog]\n"
1617             "timestamp_precision=unknown\n",
1618             /* logging_folder_empty = */ false,
1619             /* expected_error =  */
1620             "Configuration error: Timestamp precision 'unknown' is not valid. "
1621             "Valid values are: microsecond, millisecond, ms, msec, nanosecond, "
1622             "ns, nsec, s, sec, second, us, and usec"),
1623         // Unknown timestamp_precision value in the [logger] section
1624         /*1*/ /*TS_FR3_1*/
1625         LoggingConfigErrorParams(
1626             "[logger]\n"
1627             "sinks=consolelog,filelog\n"
1628             "timestamp_precision=unknown\n",
1629             /* logging_folder_empty = */ false,
1630             /* expected_error =  */
1631             "Configuration error: Timestamp precision 'unknown' is not valid. "
1632             "Valid values are: microsecond, millisecond, ms, msec, nanosecond, "
1633             "ns, nsec, s, sec, second, us, and usec"),
1634         /*2*/ /*TS_FR4_1*/
1635         LoggingConfigErrorParams("[logger]\n"
1636                                  "sinks=consolelog,filelog\n"
1637                                  "timestamp_precision=ms\n"
1638                                  "timestamp_precision=ns\n",
1639                                  /* logging_folder_empty = */ false,
1640                                  /* expected_error =  */
1641                                  "Configuration error: Option "
1642                                  "'timestamp_precision' already defined.")));
1643 #ifndef _WIN32
1644 INSTANTIATE_TEST_SUITE_P(
1645     LoggingConfigTimestampPrecisionErrorUnix, RouterLoggingConfigError,
1646     ::testing::Values(
1647         /*0*/ /* TS_HLD_1 */
1648         LoggingConfigErrorParams("[logger]\n"
1649                                  "sinks=syslog\n"
1650                                  "[syslog]\n"
1651                                  "timestamp_precision=ms\n",
1652                                  /* logging_folder_empty = */ false,
1653                                  /* expected_error =  */
1654                                  "Configuration error: timestamp_precision not "
1655                                  "valid for 'syslog'")));
1656 #else
1657 INSTANTIATE_TEST_SUITE_P(
1658     LoggingConfigTimestampPrecisionErrorWindows, RouterLoggingConfigError,
1659     ::testing::Values(
1660         /*0*/ /* TS_HLD_3 */
1661         LoggingConfigErrorParams("[logger]\n"
1662                                  "sinks=eventlog\n"
1663                                  "[eventlog]\n"
1664                                  "timestamp_precision=ms\n",
1665                                  /* logging_folder_empty = */ false,
1666                                  /* expected_error =  */
1667                                  "Configuration error: timestamp_precision not "
1668                                  "valid for 'eventlog'")));
1669 #endif
1670 
TEST_F(RouterLoggingTest,very_long_router_name_gets_properly_logged)1671 TEST_F(RouterLoggingTest, very_long_router_name_gets_properly_logged) {
1672   // This test verifies that a very long router name gets truncated in the
1673   // logged message (this is done because if it doesn't happen, the entire
1674   // message will exceed log message max length, and then the ENTIRE message
1675   // will get truncated instead. It's better to truncate the long name rather
1676   // than the stuff that follows it).
1677   // Router should report the error on STDERR and exit
1678 
1679   const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1680   TempDirectory bootstrap_dir;
1681 
1682   const auto server_port = port_pool_.get_next_available();
1683 
1684   // launch mock server and wait for it to start accepting connections
1685   auto &server_mock = launch_mysql_server_mock(json_stmts, server_port);
1686   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1687 
1688   constexpr char name[] =
1689       "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1690       "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1691       "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1692       "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1693       "verylongname";
1694   static_assert(
1695       sizeof(name) > 255,
1696       "too long");  // log message max length is 256, we want something that
1697                     // guarrantees the limit would be exceeded
1698 
1699   // launch the router in bootstrap mode
1700   auto &router = launch_router(
1701       {
1702           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1703           "--name",
1704           name,
1705           "-d",
1706           bootstrap_dir.name(),
1707       },
1708       1);
1709   // add login hook
1710   router.register_response("Please enter MySQL password for root: ",
1711                            "fake-pass\n");
1712 
1713   // wait for router to exit
1714   check_exit_code(router, EXIT_FAILURE);
1715 
1716   // expect something like this to appear on STDERR
1717   // Error: Router name
1718   // 'veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryv...'
1719   // too long (max 255).
1720   const std::string out = router.get_full_output();
1721   EXPECT_THAT(out.c_str(),
1722               HasSubstr("Error: Router name "
1723                         "'veryveryveryveryveryveryveryveryveryveryveryveryveryv"
1724                         "eryveryveryveryveryveryv...' too long (max 255)."));
1725 }
1726 
1727 /**
1728  * @test verify that debug logs are not written to console during boostrap if
1729  * bootstrap configuration file is not provided.
1730  */
TEST_F(RouterLoggingTest,is_debug_logs_disabled_if_no_bootstrap_config_file)1731 TEST_F(RouterLoggingTest, is_debug_logs_disabled_if_no_bootstrap_config_file) {
1732   const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1733 
1734   TempDirectory bootstrap_dir;
1735 
1736   const auto server_port = port_pool_.get_next_available();
1737 
1738   // launch mock server and wait for it to start accepting connections
1739   auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1740   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1741 
1742   // launch the router in bootstrap mode
1743   auto &router = launch_router({
1744       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1745       "--report-host",
1746       "dont.query.dns",
1747       "-d",
1748       bootstrap_dir.name(),
1749   });
1750 
1751   // add login hook
1752   router.register_response("Please enter MySQL password for root: ",
1753                            "fake-pass\n");
1754 
1755   // check if the bootstraping was successful
1756   check_exit_code(router, EXIT_SUCCESS);
1757   EXPECT_THAT(router.get_full_output(),
1758               testing::Not(testing::HasSubstr("Executing query:")));
1759 }
1760 
1761 /**
1762  * @test verify that debug logs are written to console during boostrap if
1763  * log_level is set to DEBUG in bootstrap configuration file.
1764  */
TEST_F(RouterLoggingTest,is_debug_logs_enabled_if_bootstrap_config_file)1765 TEST_F(RouterLoggingTest, is_debug_logs_enabled_if_bootstrap_config_file) {
1766   const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1767 
1768   TempDirectory bootstrap_dir;
1769   TempDirectory bootstrap_conf;
1770 
1771   const auto server_port = port_pool_.get_next_available();
1772 
1773   // launch mock server and wait for it to start accepting connections
1774   auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1775   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1776 
1777   // launch the router in bootstrap mode
1778   std::string logger_section = "[logger]\nlevel = DEBUG\n";
1779   auto conf_params = get_DEFAULT_defaults();
1780   // we want to log to the console
1781   conf_params["logging_folder"] = "";
1782   std::string conf_file = ProcessManager::create_config_file(
1783       bootstrap_conf.name(), logger_section, &conf_params, "bootstrap.conf", "",
1784       false);
1785 
1786   auto &router = launch_router({
1787       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1788       "--report-host",
1789       "dont.query.dns",
1790       "--force",
1791       "-d",
1792       bootstrap_dir.name(),
1793       "-c",
1794       conf_file,
1795   });
1796 
1797   // add login hook
1798   router.register_response("Please enter MySQL password for root: ",
1799                            "fake-pass\n");
1800 
1801   // check if the bootstraping was successful
1802   check_exit_code(router, EXIT_SUCCESS);
1803   EXPECT_THAT(router.get_full_output(), testing::HasSubstr("Executing query:"));
1804 }
1805 
1806 /**
1807  * @test verify that debug logs are written to mysqlrouter.log file during
1808  * bootstrap if loggin_folder is provided in bootstrap configuration file
1809  */
TEST_F(RouterLoggingTest,is_debug_logs_written_to_file_if_logging_folder)1810 TEST_F(RouterLoggingTest, is_debug_logs_written_to_file_if_logging_folder) {
1811   const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1812 
1813   TempDirectory bootstrap_dir;
1814   TempDirectory bootstrap_conf;
1815 
1816   const auto server_port = port_pool_.get_next_available();
1817 
1818   // launch mock server and wait for it to start accepting connections
1819   auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1820   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1821 
1822   // create config with logging_folder set to that directory
1823   std::map<std::string, std::string> params = {{"logging_folder", ""}};
1824   params.at("logging_folder") = bootstrap_conf.name();
1825   TempDirectory conf_dir("conf");
1826   const std::string conf_file =
1827       create_config_file(conf_dir.name(), "[logger]\nlevel = DEBUG\n", &params);
1828 
1829   auto &router = launch_router({
1830       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1831       "--report-host",
1832       "dont.query.dns",
1833       "--force",
1834       "-d",
1835       bootstrap_dir.name(),
1836       "-c",
1837       conf_file,
1838   });
1839 
1840   // add login hook
1841   router.register_response("Please enter MySQL password for root: ",
1842                            "fake-pass\n");
1843 
1844   // check if the bootstraping was successful
1845   check_exit_code(router, EXIT_SUCCESS);
1846 
1847   auto matcher = [](const std::string &line) -> bool {
1848     return line.find("Executing query:") != line.npos;
1849   };
1850 
1851   EXPECT_TRUE(find_in_file(bootstrap_conf.name() + "/mysqlrouter.log", matcher,
1852                            std::chrono::milliseconds(5000)))
1853       << router.get_full_logfile("mysqlrouter.log", bootstrap_conf.name());
1854 }
1855 
1856 /**
1857  * @test verify that normal output is written to stdout during bootstrap if
1858  * logging_folder is not provided in bootstrap configuration file.
1859  *
1860  * @test verify that logs are not written to stdout during bootstrap.
1861  */
TEST_F(RouterLoggingTest,bootstrap_normal_logs_written_to_stdout)1862 TEST_F(RouterLoggingTest, bootstrap_normal_logs_written_to_stdout) {
1863   const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1864 
1865   TempDirectory bootstrap_dir;
1866   TempDirectory bootstrap_conf;
1867 
1868   const auto server_port = port_pool_.get_next_available();
1869 
1870   // launch mock server and wait for it to start accepting connections
1871   auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1872   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1873 
1874   // launch the router in bootstrap mode
1875   std::string logger_section = "[logger]\nlevel = DEBUG\n";
1876   auto conf_params = get_DEFAULT_defaults();
1877   // we want to log to the console
1878   conf_params["logging_folder"] = "";
1879   std::string conf_file = ProcessManager::create_config_file(
1880       bootstrap_conf.name(), logger_section, &conf_params, "bootstrap.conf", "",
1881       false);
1882 
1883   auto &router = launch_router(
1884       {
1885           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1886           "--report-host",
1887           "dont.query.dns",
1888           "--force",
1889           "-d",
1890           bootstrap_dir.name(),
1891           "-c",
1892           conf_file,
1893       },
1894       0, /* expected error code */
1895       false /*false = capture only stdout*/);
1896 
1897   // add login hook
1898   router.register_response("Please enter MySQL password for root: ",
1899                            "fake-pass\n");
1900 
1901   // check if the bootstraping was successful
1902   check_exit_code(router, EXIT_SUCCESS);
1903 
1904   // check if logs are not written to output
1905   EXPECT_THAT(router.get_full_output(),
1906               testing::Not(testing::HasSubstr("Executing query:")));
1907 
1908   // check if normal output is written to output
1909   EXPECT_THAT(router.get_full_output(),
1910               testing::HasSubstr("After this MySQL Router has been started "
1911                                  "with the generated configuration"));
1912 
1913   EXPECT_THAT(router.get_full_output(),
1914               testing::HasSubstr("MySQL Classic protocol"));
1915 
1916   EXPECT_THAT(router.get_full_output(), testing::HasSubstr("MySQL X protocol"));
1917 }
1918 
1919 class MetadataCacheLoggingTest : public RouterLoggingTest {
1920  protected:
SetUp()1921   void SetUp() override {
1922     RouterLoggingTest::SetUp();
1923 
1924     mysql_harness::DIM &dim = mysql_harness::DIM::instance();
1925     // RandomGenerator
1926     dim.set_RandomGenerator(
1927         []() {
1928           static mysql_harness::RandomGenerator rg;
1929           return &rg;
1930         },
1931         [](mysql_harness::RandomGeneratorInterface *) {});
1932 
1933     cluster_nodes_ports = {port_pool_.get_next_available(),
1934                            port_pool_.get_next_available(),
1935                            port_pool_.get_next_available()};
1936     cluster_nodes_http_ports = {port_pool_.get_next_available(),
1937                                 port_pool_.get_next_available(),
1938                                 port_pool_.get_next_available()};
1939     router_port = port_pool_.get_next_available();
1940     metadata_cache_section = get_metadata_cache_section(cluster_nodes_ports);
1941     routing_section =
1942         get_metadata_cache_routing_section("PRIMARY", "round-robin", "");
1943   }
1944 
get_metadata_cache_section(std::vector<uint16_t> ports)1945   std::string get_metadata_cache_section(std::vector<uint16_t> ports) {
1946     std::string metadata_caches = "bootstrap_server_addresses=";
1947 
1948     for (auto it = ports.begin(); it != ports.end(); ++it) {
1949       metadata_caches += (it == ports.begin()) ? "" : ",";
1950       metadata_caches += "mysql://localhost:" + std::to_string(*it);
1951     }
1952     metadata_caches += "\n";
1953 
1954     return "[metadata_cache:test]\n"
1955            "router_id=1\n" +
1956            metadata_caches +
1957            "user=mysql_router1_user\n"
1958            "metadata_cluster=test\n"
1959            "connect_timeout=1\n"
1960            "ttl=0.1\n\n";
1961   }
1962 
get_metadata_cache_routing_section(const std::string & role,const std::string & strategy,const std::string & mode="")1963   std::string get_metadata_cache_routing_section(const std::string &role,
1964                                                  const std::string &strategy,
1965                                                  const std::string &mode = "") {
1966     std::string result =
1967         "[routing:test_default]\n"
1968         "bind_port=" +
1969         std::to_string(router_port) + "\n" +
1970         "destinations=metadata-cache://test/default?role=" + role + "\n" +
1971         "protocol=classic\n";
1972 
1973     if (!strategy.empty())
1974       result += std::string("routing_strategy=" + strategy + "\n");
1975     if (!mode.empty()) result += std::string("mode=" + mode + "\n");
1976 
1977     return result;
1978   }
1979 
init_keyring_and_config_file(const std::string & conf_dir,bool log_to_console=false)1980   std::string init_keyring_and_config_file(const std::string &conf_dir,
1981                                            bool log_to_console = false) {
1982     auto default_section = get_DEFAULT_defaults();
1983     init_keyring(default_section, temp_test_dir.name());
1984     default_section["logging_folder"] =
1985         log_to_console ? "" : get_logging_dir().str();
1986     return create_config_file(
1987         conf_dir,
1988         "[logger]\nlevel = DEBUG\n" + metadata_cache_section + routing_section,
1989         &default_section);
1990   }
1991 
1992   TempDirectory temp_test_dir;
1993   std::vector<uint16_t> cluster_nodes_ports;
1994   std::vector<uint16_t> cluster_nodes_http_ports;
1995   uint16_t router_port;
1996   std::string metadata_cache_section;
1997   std::string routing_section;
1998 };
1999 
2000 /**
2001  * @test verify if error message is logged if router cannot connect to any
2002  *       metadata server.
2003  */
TEST_F(MetadataCacheLoggingTest,log_error_when_cannot_connect_to_any_metadata_server)2004 TEST_F(MetadataCacheLoggingTest,
2005        log_error_when_cannot_connect_to_any_metadata_server) {
2006   TempDirectory conf_dir;
2007 
2008   // launch the router with metadata-cache configuration
2009   auto &router = ProcessManager::launch_router(
2010       {"-c", init_keyring_and_config_file(conf_dir.name())});
2011   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2012 
2013   // expect something like this to appear on STDERR
2014   // 2017-12-21 17:22:35 metadata_cache ERROR [7ff0bb001700] Failed connecting
2015   // with any of the 3 metadata servers
2016   auto matcher = [](const std::string &line) -> bool {
2017     return line.find("metadata_cache ERROR") != line.npos &&
2018            line.find(
2019                "Failed fetching metadata from any of the 3 metadata servers") !=
2020                line.npos;
2021   };
2022 
2023   auto log_file = get_logging_dir();
2024   log_file.append("mysqlrouter.log");
2025   EXPECT_TRUE(
2026       find_in_file(log_file.str(), matcher, std::chrono::milliseconds(5000)))
2027       << router.get_full_logfile();
2028 }
2029 
2030 /**
2031  * @test verify if appropriate warning messages are logged when cannot connect
2032  * to first metadata server, but can connect to another one.
2033  */
TEST_F(MetadataCacheLoggingTest,log_warning_when_cannot_connect_to_first_metadata_server)2034 TEST_F(MetadataCacheLoggingTest,
2035        log_warning_when_cannot_connect_to_first_metadata_server) {
2036   TempDirectory conf_dir("conf");
2037 
2038   // launch second metadata server
2039   const auto http_port = cluster_nodes_http_ports[1];
2040   auto &server = launch_mysql_server_mock(
2041       get_data_dir().join("metadata_3_nodes_first_not_accessible.js").str(),
2042       cluster_nodes_ports[1], EXIT_SUCCESS, false, http_port);
2043   ASSERT_NO_FATAL_FAILURE(check_port_ready(server, cluster_nodes_ports[1]));
2044   EXPECT_TRUE(MockServerRestClient(http_port).wait_for_rest_endpoint_ready());
2045   set_mock_metadata(http_port, "", cluster_nodes_ports);
2046 
2047   // launch the router with metadata-cache configuration
2048   auto &router = ProcessManager::launch_router(
2049       {"-c", init_keyring_and_config_file(conf_dir.name())});
2050   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
2051 
2052   // expect something like this to appear on STDERR
2053   // 2017-12-21 17:22:35 metadata_cache WARNING [7ff0bb001700] Failed connecting
2054   // with Metadata Server 127.0.0.1:7002: Can't connect to MySQL server on
2055   // '127.0.0.1' (111) (2003)
2056   auto info_matcher = [&](const std::string &line) -> bool {
2057     return line.find("metadata_cache WARNING") != line.npos &&
2058            line.find("Failed connecting with Metadata Server 127.0.0.1:" +
2059                      std::to_string(cluster_nodes_ports[0])) != line.npos;
2060   };
2061 
2062   EXPECT_TRUE(find_in_file(get_logging_dir().str() + "/mysqlrouter.log",
2063                            info_matcher, 10000ms))
2064       << router.get_full_logfile();
2065 
2066   auto warning_matcher = [](const std::string &line) -> bool {
2067     return line.find("metadata_cache WARNING") != line.npos &&
2068            line.find(
2069                "While updating metadata, could not establish a connection to "
2070                "replicaset") != line.npos;
2071   };
2072   EXPECT_TRUE(find_in_file(get_logging_dir().str() + "/mysqlrouter.log",
2073                            warning_matcher, 10000ms))
2074       << router.get_full_logfile();
2075 }
2076 
2077 #ifndef _WIN32
2078 /**
2079  * @test Checks that the logs rotation works (meaning Router will recreate
2080  * it's log file when it was moved and HUP singnal was sent to the Router).
2081  */
TEST_F(MetadataCacheLoggingTest,log_rotation_by_HUP_signal)2082 TEST_F(MetadataCacheLoggingTest, log_rotation_by_HUP_signal) {
2083   TempDirectory conf_dir;
2084 
2085   // launch the router with metadata-cache configuration
2086   auto &router = ProcessManager::launch_router(
2087       {"-c", init_keyring_and_config_file(conf_dir.name())});
2088   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2089 
2090   std::this_thread::sleep_for(500ms);
2091 
2092   auto log_file = get_logging_dir();
2093   log_file.append("mysqlrouter.log");
2094 
2095   EXPECT_TRUE(log_file.exists());
2096 
2097   // now let's simulate what logrotate script does
2098   // move the log_file appending '.1' to its name
2099   auto log_file_1 = get_logging_dir();
2100   log_file_1.append("mysqlrouter.log.1");
2101   mysqlrouter::rename_file(log_file.str(), log_file_1.str());
2102   const auto pid = static_cast<pid_t>(router.get_pid());
2103   ::kill(pid, SIGHUP);
2104 
2105   // let's wait  until something new gets logged (metadata cache TTL has
2106   // expired), to be sure the default file that we moved is back.
2107   // Now both old and new files should exist
2108   unsigned retries = 10;
2109   const auto kSleep = 100ms;
2110   do {
2111     std::this_thread::sleep_for(kSleep);
2112   } while ((--retries > 0) && !log_file.exists());
2113 
2114   EXPECT_TRUE(log_file.exists()) << router.get_full_logfile();
2115   EXPECT_TRUE(log_file_1.exists());
2116 }
2117 
2118 /**
2119  * @test Checks that the Router continues to log to the file when the
2120  * SIGHUP gets sent to it and no file replacement is done.
2121  */
TEST_F(MetadataCacheLoggingTest,log_rotation_by_HUP_signal_no_file_move)2122 TEST_F(MetadataCacheLoggingTest, log_rotation_by_HUP_signal_no_file_move) {
2123   TempDirectory conf_dir;
2124 
2125   // launch the router with metadata-cache configuration
2126   auto &router = ProcessManager::launch_router(
2127       {"-c", init_keyring_and_config_file(conf_dir.name())});
2128   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2129 
2130   std::this_thread::sleep_for(500ms);
2131 
2132   auto log_file = get_logging_dir();
2133   log_file.append("mysqlrouter.log");
2134 
2135   EXPECT_TRUE(log_file.exists());
2136 
2137   // grab the current log content
2138   const std::string log_content = router.get_full_logfile();
2139 
2140   // send the log-rotate signal
2141   const auto pid = static_cast<pid_t>(router.get_pid());
2142   ::kill(pid, SIGHUP);
2143 
2144   // wait until something new gets logged;
2145   std::string log_content_2;
2146   unsigned step = 0;
2147   do {
2148     std::this_thread::sleep_for(100ms);
2149     log_content_2 = router.get_full_logfile();
2150   } while ((log_content_2 == log_content) && (step++ < 20));
2151 
2152   // The logfile should still exist
2153   EXPECT_TRUE(log_file.exists());
2154   // It should still contain what was there before and more (Router should keep
2155   // logging)
2156   EXPECT_THAT(log_content_2, StartsWith(log_content));
2157   EXPECT_STRNE(log_content_2.c_str(), log_content.c_str());
2158 }
2159 
2160 /**
2161  * @test Checks that the logs Router continues to log to the file when the
2162  * SIGHUP gets sent to it and no file replacement is done.
2163  */
TEST_F(MetadataCacheLoggingTest,log_rotation_when_router_restarts)2164 TEST_F(MetadataCacheLoggingTest, log_rotation_when_router_restarts) {
2165   TempDirectory conf_dir;
2166 
2167   // launch the router with metadata-cache configuration
2168   auto &router = ProcessManager::launch_router(
2169       {"-c", init_keyring_and_config_file(conf_dir.name())});
2170   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2171 
2172   std::this_thread::sleep_for(500ms);
2173 
2174   auto log_file = get_logging_dir();
2175   log_file.append("mysqlrouter.log");
2176 
2177   EXPECT_TRUE(log_file.exists());
2178 
2179   // now stop the router
2180   int res = router.kill();
2181   EXPECT_EQ(EXIT_SUCCESS, res) << router.get_full_output();
2182 
2183   // move the log_file appending '.1' to its name
2184   auto log_file_1 = get_logging_dir();
2185   log_file_1.append("mysqlrouter.log.1");
2186   mysqlrouter::rename_file(log_file.str(), log_file_1.str());
2187 
2188   // make the new file read-only
2189   chmod(log_file_1.c_str(), S_IRUSR);
2190 
2191   // start the router again and check that the new log file got created
2192   auto &router2 = ProcessManager::launch_router(
2193       {"-c", init_keyring_and_config_file(conf_dir.name())});
2194   ASSERT_NO_FATAL_FAILURE(check_port_ready(router2, router_port, 10000ms));
2195   std::this_thread::sleep_for(500ms);
2196   EXPECT_TRUE(log_file.exists());
2197 }
2198 
2199 /**
2200  * @test Checks that the logs Router continues to log to the file when the
2201  * SIGHUP gets sent to it and no file replacement is done.
2202  */
TEST_F(MetadataCacheLoggingTest,log_rotation_read_only)2203 TEST_F(MetadataCacheLoggingTest, log_rotation_read_only) {
2204   TempDirectory conf_dir;
2205 
2206   // launch the router with metadata-cache configuration
2207   auto &router = ProcessManager::launch_router(
2208       {"-c", init_keyring_and_config_file(conf_dir.name())}, EXIT_FAILURE);
2209   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2210 
2211   auto log_file = get_logging_dir();
2212   log_file.append("mysqlrouter.log");
2213 
2214   unsigned retries = 5;
2215   const auto kSleep = 100ms;
2216   do {
2217     std::this_thread::sleep_for(kSleep);
2218   } while ((--retries > 0) && !log_file.exists());
2219 
2220   EXPECT_TRUE(log_file.exists());
2221 
2222   // move the log_file appending '.1' to its name
2223   auto log_file_1 = get_logging_dir();
2224   log_file_1.append("mysqlrouter.log.1");
2225   mysqlrouter::rename_file(log_file.str(), log_file_1.str());
2226 
2227   // "manually" recreate the log file and make it read only
2228   {
2229     std::ofstream logf(log_file.str());
2230     EXPECT_TRUE(logf.good());
2231   }
2232   chmod(log_file.c_str(), S_IRUSR);
2233 
2234   // send the log-rotate signal
2235   const auto pid = static_cast<pid_t>(router.get_pid());
2236   ::kill(pid, SIGHUP);
2237 
2238   // we expect the router to exit,
2239   // as the logfile is no longer usable it will fallback to logging to the
2240   // stderr
2241   check_exit_code(router, EXIT_FAILURE);
2242   EXPECT_THAT(router.get_full_output(),
2243               HasSubstr("File exists, but cannot open for writing"));
2244   EXPECT_THAT(router.get_full_output(), HasSubstr("Unloading all plugins."));
2245 }
2246 
2247 /**
2248  * @test Checks that the logs rotation does not cause any crash in case of
2249  * not logging to the file (logging_foler empty == logging to the std:cerr)
2250  */
TEST_F(MetadataCacheLoggingTest,log_rotation_stdout)2251 TEST_F(MetadataCacheLoggingTest, log_rotation_stdout) {
2252   TempDirectory conf_dir;
2253 
2254   // launch the router with metadata-cache configuration
2255   auto &router = ProcessManager::launch_router(
2256       {"-c",
2257        init_keyring_and_config_file(conf_dir.name(), /*log_to_console=*/true)});
2258   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2259 
2260   std::this_thread::sleep_for(200ms);
2261   const auto pid = static_cast<pid_t>(router.get_pid());
2262   ::kill(pid, SIGHUP);
2263   std::this_thread::sleep_for(200ms);
2264 }
2265 
2266 #endif
2267 
2268 /**************************************************/
2269 /* Tests for valid logger filename configurations */
2270 /**************************************************/
2271 
2272 #define DEFAULT_LOGFILE_NAME "mysqlrouter.log"
2273 #define USER_LOGFILE_NAME "foo.log"
2274 #define USER_LOGFILE_NAME_2 "bar.log"
2275 
2276 struct LoggingConfigFilenameOkParams {
2277   std::string logger_config;
2278   std::string filename;
2279   bool console_to_stderr;
2280 
LoggingConfigFilenameOkParamsLoggingConfigFilenameOkParams2281   LoggingConfigFilenameOkParams(const std::string &logger_config_,
2282                                 const std::string filename_)
2283       : logger_config(logger_config_),
2284         filename(filename_),
2285         console_to_stderr(true) {}
2286 
LoggingConfigFilenameOkParamsLoggingConfigFilenameOkParams2287   LoggingConfigFilenameOkParams(const std::string &logger_config_,
2288                                 const std::string filename_,
2289                                 bool console_to_stderr_)
2290       : logger_config(logger_config_),
2291         filename(filename_),
2292         console_to_stderr(console_to_stderr_) {}
2293 };
2294 
2295 class RouterLoggingTestConfigFilename
2296     : public RouterLoggingTest,
2297       public ::testing::WithParamInterface<LoggingConfigFilenameOkParams> {};
2298 
2299 /** @test This test verifies that a proper log filename is written to
2300  * for various sinks/filename combinations.
2301  */
TEST_P(RouterLoggingTestConfigFilename,LoggingTestConfigFilename)2302 TEST_P(RouterLoggingTestConfigFilename, LoggingTestConfigFilename) {
2303   auto test_params = GetParam();
2304 
2305   TempDirectory tmp_dir;
2306   auto conf_params = get_DEFAULT_defaults();
2307   conf_params["logging_folder"] = tmp_dir.name();
2308 
2309   TempDirectory conf_dir("conf");
2310   const std::string conf_text = "[routing]\n\n" + test_params.logger_config;
2311   const std::string conf_file =
2312       create_config_file(conf_dir.name(), conf_text, &conf_params);
2313 
2314   // empty routing section results in a failure, but while logging to file
2315   auto &router = launch_router({"-c", conf_file}, 1);
2316   check_exit_code(router, EXIT_FAILURE);
2317 
2318   // check the file log if it contains what's expected
2319   const std::string file_log_txt =
2320       router.get_full_logfile(test_params.filename, tmp_dir.name());
2321 
2322   EXPECT_THAT(file_log_txt, HasSubstr("plugin 'routing' init failed"))
2323       << "\file_log_txt:\n"
2324       << file_log_txt;
2325 }
2326 
2327 INSTANTIATE_TEST_SUITE_P(
2328     LoggingTestConfigFilename, RouterLoggingTestConfigFilename,
2329     ::testing::Values(
2330         // default filename in logger section
2331         /*0*/
2332         LoggingConfigFilenameOkParams("[logger]\n"
2333                                       "filename=" DEFAULT_LOGFILE_NAME "\n",
2334                                       DEFAULT_LOGFILE_NAME),
2335         // TS_FR01_01 user defined logfile name in logger section
2336         /*1*/
2337         LoggingConfigFilenameOkParams("[logger]\n"
2338                                       "filename=" USER_LOGFILE_NAME "\n",
2339                                       USER_LOGFILE_NAME),
2340         // TS_FR01_02 user defined logfile name in filelog sink
2341         /*2*/
2342         LoggingConfigFilenameOkParams("[logger]\n"
2343                                       "sinks=filelog\n"
2344                                       "[filelog]\n"
2345                                       "filename=" USER_LOGFILE_NAME "\n",
2346                                       USER_LOGFILE_NAME),
2347         // TS_FR04_09 user defined logfile name in filelog sink overrides user
2348         // defined logfile name in logger section
2349         /*3*/
2350         LoggingConfigFilenameOkParams("[logger]\n"
2351                                       "sinks=filelog\n"
2352                                       "filename=" USER_LOGFILE_NAME "\n"
2353                                       "[filelog]\n"
2354                                       "filename=" USER_LOGFILE_NAME_2 "\n",
2355                                       USER_LOGFILE_NAME_2),
2356         // TS_FR05_01 empty logger filename logs to default logfile name
2357         /*4*/
2358         LoggingConfigFilenameOkParams("[logger]\n"
2359                                       "filename=\n",
2360                                       DEFAULT_LOGFILE_NAME),
2361         // TS_FR05_02 empty filelog filename logs to default logfile name
2362         /*5*/
2363         LoggingConfigFilenameOkParams("[logger]\n"
2364                                       "sinks=filelog\n"
2365                                       "[filelog]\n"
2366                                       "filename=\n",
2367                                       DEFAULT_LOGFILE_NAME),
2368         // TS_FR04_11 empty filelog filename logs to userdefined logger filename
2369         /*6*/
2370         LoggingConfigFilenameOkParams("[logger]\n"
2371                                       "filename=" USER_LOGFILE_NAME "\n"
2372                                       "sinks=filelog\n"
2373                                       "[filelog]\n"
2374                                       "filename=\n",
2375                                       USER_LOGFILE_NAME),
2376         // TS_FR04_12 undefined filelog filename logs to userdefined value for
2377         // logger filename
2378         /*7*/
2379         LoggingConfigFilenameOkParams("[logger]\n"
2380                                       "filename=" USER_LOGFILE_NAME "\n"
2381                                       "sinks=filelog\n"
2382                                       "[filelog]\n",
2383                                       USER_LOGFILE_NAME),
2384         // user defined logfile name in filelog sink overrides logger section
2385         /*8*/
2386         LoggingConfigFilenameOkParams("[logger]\n"
2387                                       "sinks=filelog\n"
2388                                       "filename=" DEFAULT_LOGFILE_NAME "\n"
2389                                       "[filelog]\n"
2390                                       "filename=" USER_LOGFILE_NAME "\n",
2391                                       USER_LOGFILE_NAME),
2392         // TS_FR04_01 empty filename has no effect
2393         /*9*/
2394         LoggingConfigFilenameOkParams("[logger]\n"
2395                                       "sinks=filelog\n"
2396                                       "filename=\n"
2397                                       "[filelog]\n"
2398                                       "filename=" USER_LOGFILE_NAME_2 "\n",
2399                                       USER_LOGFILE_NAME_2),
2400         // TS_FR04_03 empty filenames has no effect, and logs to default
2401         /*10*/
2402         LoggingConfigFilenameOkParams("[logger]\n"
2403                                       "sinks=filelog\n"
2404                                       "filename=\n"
2405                                       "[filelog]\n"
2406                                       "filename=\n",
2407                                       DEFAULT_LOGFILE_NAME),
2408         // TS_FR04_04 no filenames results in logging to default
2409         /*11*/
2410         LoggingConfigFilenameOkParams("[logger]\n"
2411                                       "sinks=filelog\n"
2412                                       "[filelog]\n",
2413                                       DEFAULT_LOGFILE_NAME)));
2414 
2415 #define NOT_USED ""
2416 
2417 #ifndef WIN32
2418 #define NULL_DEVICE_NAME "/dev/null"
2419 #define STDOUT_DEVICE_NAME "/dev/stdout"
2420 #define STDERR_DEVICE_NAME "/dev/stderr"
2421 #else
2422 #define NULL_DEVICE_NAME "NUL"
2423 #define STDOUT_DEVICE_NAME "CON"
2424 // No STDERR equivalent for WIN32
2425 #endif
2426 
2427 class RouterLoggingTestConfigFilenameDevices
2428     : public RouterLoggingTest,
2429       public ::testing::WithParamInterface<LoggingConfigFilenameOkParams> {};
2430 
2431 /** @test This test verifies that consolelog destination may be set to various
2432  * devices
2433  */
TEST_P(RouterLoggingTestConfigFilenameDevices,LoggingTestConsoleDestinationDevices)2434 TEST_P(RouterLoggingTestConfigFilenameDevices,
2435        LoggingTestConsoleDestinationDevices) {
2436   // FIXME: Unfortunately due to the limitations of our component testing
2437   // framework, this test has a flaw: it is not possible to distinguish if the
2438   // output returned from router.get_full_output() appeared on STDERR or STDOUT.
2439   // This should be fixed in the future.
2440   auto test_params = GetParam();
2441   bool console_empty =
2442       (test_params.filename.compare(NULL_DEVICE_NAME) == 0 ? true : false);
2443 
2444   Path destination(test_params.filename);
2445 #ifndef WIN32
2446   EXPECT_TRUE(destination.exists());
2447 #endif
2448 
2449   TempDirectory tmp_dir;
2450   auto conf_params = get_DEFAULT_defaults();
2451   conf_params["logging_folder"] = tmp_dir.name();
2452 
2453   TempDirectory conf_dir("conf");
2454   const std::string conf_text =
2455       "[routing]\n\n[logger]\nsinks=consolelog\n[consolelog]\ndestination=" +
2456       destination.str();
2457   const std::string conf_file =
2458       create_config_file(conf_dir.name(), conf_text, &conf_params);
2459 
2460   // empty routing section results in a failure, but while logging to file
2461   auto &router =
2462       launch_router({"-c", conf_file}, 1, test_params.console_to_stderr);
2463   check_exit_code(router, EXIT_FAILURE);
2464 
2465   const std::string console_log_txt = router.get_full_output();
2466   if (console_empty) {
2467     // Expect the console log to be empty
2468     EXPECT_TRUE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2469   } else {
2470     // Expect the console log to not be empty
2471     EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2472   }
2473 
2474   // expect no default router file created in the logging folder
2475   Path shouldnotexist = Path(tmp_dir.name()).join(DEFAULT_LOGFILE_NAME);
2476   EXPECT_FALSE(shouldnotexist.exists());
2477   shouldnotexist = Path("/dev").join(DEFAULT_LOGFILE_NAME);
2478   EXPECT_FALSE(shouldnotexist.exists());
2479 
2480 #ifndef WIN32
2481   EXPECT_TRUE(destination.exists());
2482 #endif
2483 }
2484 
2485 INSTANTIATE_TEST_SUITE_P(
2486     LoggingTestConsoleDestinationDevices,
2487     RouterLoggingTestConfigFilenameDevices,
2488     ::testing::Values(
2489         // TS_FR07_03 consolelog destination /dev/null
2490         /*0*/
2491         LoggingConfigFilenameOkParams(NOT_USED, NULL_DEVICE_NAME, true),
2492         // TS_FR07_01 consolelog destination /dev/stdout
2493         /*1*/
2494         LoggingConfigFilenameOkParams(NOT_USED, STDOUT_DEVICE_NAME, false)));
2495 
2496 #ifndef WIN32
2497 INSTANTIATE_TEST_SUITE_P(
2498     LoggingTestConsoleDestinationDevicesUnix,
2499     RouterLoggingTestConfigFilenameDevices,
2500     ::testing::Values(
2501         // TS_FR07_02 consolelog destination /dev/stderr
2502         /*0*/
2503         LoggingConfigFilenameOkParams(NOT_USED, STDERR_DEVICE_NAME, true)));
2504 #endif
2505 
2506 struct LoggingConfigFilenameErrorParams {
2507   std::string logger_config;
2508   std::string filename;
2509   bool create_file;
2510   std::string expected_error;
2511 
LoggingConfigFilenameErrorParamsLoggingConfigFilenameErrorParams2512   LoggingConfigFilenameErrorParams(const std::string &logger_config_,
2513                                    const std::string filename_,
2514                                    bool create_file_,
2515                                    const std::string expected_error_)
2516       : logger_config(logger_config_),
2517         filename(filename_),
2518         create_file(create_file_),
2519         expected_error(expected_error_) {}
2520 };
2521 
2522 class RouterLoggingConfigFilenameError
2523     : public RouterLoggingTest,
2524       public ::testing::WithParamInterface<LoggingConfigFilenameErrorParams> {};
2525 
2526 #define ABS_PATH "%%ABSPATH%%"
2527 #define ABS_DIR "%%ABSDIR%%"
2528 #define REL_PATH "%%RELPATH%%"
2529 #define REL_DIR "%%RELDIR%%"
2530 #define FILENAME "%%FILENAME%%"
2531 
2532 /** @test This test verifies that absolute and relative filenames are rejected
2533  * in filename option for various sinks/filename combinations.
2534  */
TEST_P(RouterLoggingConfigFilenameError,LoggingConfigAbsRelFilenameError)2535 TEST_P(RouterLoggingConfigFilenameError, LoggingConfigAbsRelFilenameError) {
2536   auto test_params = GetParam();
2537 
2538   TempDirectory tmp_dir;
2539 
2540   // create the absolute and relative paths (note: order)
2541   Path abs_dir = Path(tmp_dir.name()).real_path();
2542   Path abs_path = abs_dir.join(test_params.filename);
2543   Path rel_path = Path(tmp_dir.name()).basename().join(test_params.filename);
2544 
2545   auto conf_params = get_DEFAULT_defaults();
2546   conf_params["logging_folder"] = abs_dir.str();
2547 
2548   // Create tmp_file once the tmp_dir is created. Removed by tmp_dir dtor.
2549   if (test_params.create_file) {
2550     std::ofstream myfile_;
2551     myfile_.open(abs_path.str());
2552     if (myfile_.is_open()) {
2553       myfile_ << "Temporary file created by router test ...\n";
2554       myfile_.flush();
2555       myfile_.close();
2556     }
2557     EXPECT_TRUE(abs_path.exists());
2558   }
2559 
2560   // replace the pattern in config where applicable
2561   std::string cfg = "[keepalive]\n\n" + test_params.logger_config;
2562   while (cfg.find(FILENAME) != std::string::npos) {
2563     cfg.replace(cfg.find(FILENAME), sizeof(FILENAME) - 1,
2564                 test_params.filename.c_str());
2565   }
2566   while (cfg.find(ABS_PATH) != std::string::npos) {
2567     cfg.replace(cfg.find(ABS_PATH), sizeof(ABS_PATH) - 1, abs_path.c_str());
2568   }
2569   while (cfg.find(ABS_DIR) != std::string::npos) {
2570     cfg.replace(cfg.find(ABS_DIR), sizeof(ABS_DIR) - 1, abs_dir.c_str());
2571   }
2572   while (cfg.find(REL_PATH) != std::string::npos) {
2573     cfg.replace(cfg.find(REL_PATH), sizeof(REL_PATH) - 1, rel_path.c_str());
2574   }
2575 
2576   TempDirectory conf_dir("conf");
2577   const std::string conf_file =
2578       create_config_file(conf_dir.name(), cfg, &conf_params);
2579 
2580   // empty routing section results in a failure, but while logging to file
2581   auto &router = launch_router({"-c", conf_file}, 1);
2582   check_exit_code(router, EXIT_FAILURE);
2583 
2584   // the error happens during the logger initialization so we expect the message
2585   // on the console which is the default sink until we switch to the
2586   // configuration from the config file
2587   const std::string console_log_txt = router.get_full_output();
2588 
2589   EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2590 
2591   EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
2592       << "\nconsole:\n"
2593       << console_log_txt;
2594 
2595   // expect no default router file created in the logging folder
2596   Path shouldnotexist = Path(abs_dir.str()).join(DEFAULT_LOGFILE_NAME);
2597   EXPECT_FALSE(shouldnotexist.exists());
2598 
2599   if (!test_params.create_file) {
2600     EXPECT_FALSE(abs_path.exists());
2601   }
2602 }
2603 
2604 INSTANTIATE_TEST_SUITE_P(
2605     LoggingConfigAbsRelFilenameError, RouterLoggingConfigFilenameError,
2606     ::testing::Values(
2607         // TS_FR02_01 filename with relative path in logger
2608         /*0*/ LoggingConfigFilenameErrorParams(
2609             "[logger]\n"
2610             "filename=" REL_PATH "\n",
2611             USER_LOGFILE_NAME, false, "must be a filename, not a path"),
2612         // TS_FR02_02 filename with relative path in filelog
2613         /*1*/
2614         LoggingConfigFilenameErrorParams("[logger]\n"
2615                                          "sinks=filelog\n"
2616                                          "[filelog]\n"
2617                                          "filename=" REL_PATH "\n",
2618                                          USER_LOGFILE_NAME, false,
2619                                          "must be a filename, not a path"),
2620         // TS_FR02_03 absolute filename in logger
2621         /*2*/
2622         LoggingConfigFilenameErrorParams("[logger]\n"
2623                                          "filename=" ABS_PATH "\n",
2624                                          USER_LOGFILE_NAME, false,
2625                                          "must be a filename, not a path"),
2626         // TS_FR02_04 absolute filename in filelog
2627         /*3*/
2628         LoggingConfigFilenameErrorParams("[logger]\n"
2629                                          "sinks=filelog\n"
2630                                          "[filelog]\n"
2631                                          "filename=" ABS_PATH "\n",
2632                                          USER_LOGFILE_NAME, false,
2633                                          "must be a filename, not a path"),
2634         // TS_FR02_05 slash filename in logger
2635         /*4*/
2636         LoggingConfigFilenameErrorParams("[logger]\n"
2637                                          "filename=/\n",
2638                                          USER_LOGFILE_NAME, false,
2639                                          "is not a valid log filename"),
2640         // TS_FR02_06 slash filename in filelog
2641         /*5*/
2642         LoggingConfigFilenameErrorParams("[logger]\n"
2643                                          "sinks=filelog\n"
2644                                          "[filelog]\n"
2645                                          "filename=/\n",
2646                                          USER_LOGFILE_NAME, false,
2647                                          "is not a valid log filename"),
2648         // TS_FR02_07 existing folder filename in filelog
2649         /*6*/
2650         LoggingConfigFilenameErrorParams("[logger]\n"
2651                                          "filename=" ABS_DIR "\n",
2652                                          USER_LOGFILE_NAME, false,
2653                                          "must be a filename, not a path"),
2654         // TS_FR02_08 existing folder filename in filelog
2655         /*7*/
2656         LoggingConfigFilenameErrorParams("[logger]\n"
2657                                          "sinks=filelog\n"
2658                                          "[filelog]\n"
2659                                          "filename=" ABS_DIR "\n",
2660                                          USER_LOGFILE_NAME, false,
2661                                          "must be a filename, not a path"),
2662         // TS_FR02_09 dot filename in logger
2663         /*8*/
2664         LoggingConfigFilenameErrorParams(
2665             "[logger]\n"
2666             "filename=.\n",
2667             USER_LOGFILE_NAME, false,
2668             "File exists, but cannot open for writing"),
2669         // TS_FR02_10 dot filename in filelog
2670         /*9*/
2671         LoggingConfigFilenameErrorParams(
2672             "[logger]\n"
2673             "sinks=filelog\n"
2674             "[filelog]\n"
2675             "filename=.\n",
2676             USER_LOGFILE_NAME, false,
2677             "File exists, but cannot open for writing"),
2678         // TS_FR04_10 filename /path triggers warning and not silent override
2679         /*10*/
2680         LoggingConfigFilenameErrorParams("[logger]\n"
2681                                          "filename=" USER_LOGFILE_NAME "\n"
2682                                          "sinks=filelog\n"
2683                                          "[filelog]\n"
2684                                          "filename=" ABS_DIR "\n",
2685                                          USER_LOGFILE_NAME, false,
2686                                          "must be a filename, not a path"),
2687         // TS_FR04_02 empty filename has no effect
2688         /*11*/
2689         LoggingConfigFilenameErrorParams("[logger]\n"
2690                                          "filename=\n"
2691                                          "sinks=filelog\n"
2692                                          "[filelog]\n"
2693                                          "filename=" ABS_DIR "\n",
2694                                          USER_LOGFILE_NAME, false,
2695                                          "must be a filename, not a path"),
2696         // TS_FR04_06 Verify [logger].filename=/path or [filelog].filename
2697         // triggers an error
2698         /*12*/
2699         LoggingConfigFilenameErrorParams("[logger]\n"
2700                                          "filename=" ABS_DIR "\n"
2701                                          "sinks=filelog\n"
2702                                          "[filelog]\n"
2703                                          "filename=" ABS_DIR "\n",
2704                                          USER_LOGFILE_NAME, false,
2705                                          "must be a filename, not a path"),
2706         // TS_FR04_07 Verify [logger].filename=/path triggers an error
2707         /*13*/
2708         LoggingConfigFilenameErrorParams("[logger]\n"
2709                                          "filename=" ABS_DIR "\n"
2710                                          "sinks=filelog\n"
2711                                          "[filelog]\n"
2712                                          "filename=\n",
2713                                          USER_LOGFILE_NAME, false,
2714                                          "must be a filename, not a path"),
2715         // TS_FR04_08 Verify [logger].filename=/path triggers an error
2716         /*14*/
2717         LoggingConfigFilenameErrorParams("[logger]\n"
2718                                          "filename=" ABS_DIR "\n"
2719                                          "sinks=filelog\n"
2720                                          "[filelog]\n",
2721                                          USER_LOGFILE_NAME, false,
2722                                          "must be a filename, not a path"),
2723         // TS_FR10_01 consolelog destination set to existing file
2724         /*15*/
2725         LoggingConfigFilenameErrorParams("[logger]\n"
2726                                          "sinks=consolelog\n"
2727                                          "[consolelog]\n"
2728                                          "destination=" FILENAME "\n",
2729                                          USER_LOGFILE_NAME, true,
2730                                          "Illegal destination"),
2731         // TS_FR10_02 consolelog destination set to non-existing file
2732         /*16*/
2733         LoggingConfigFilenameErrorParams("[logger]\n"
2734                                          "sinks=consolelog\n"
2735                                          "[consolelog]\n"
2736                                          "destination=" FILENAME "\n",
2737                                          USER_LOGFILE_NAME, false,
2738                                          "Illegal destination"),
2739         // TS_FR10_03 consolelog destination set to realtive file
2740         /*17*/
2741         LoggingConfigFilenameErrorParams("[logger]\n"
2742                                          "sinks=consolelog\n"
2743                                          "[consolelog]\n"
2744                                          "destination=" REL_PATH "\n",
2745                                          USER_LOGFILE_NAME, true,
2746                                          "Illegal destination"),
2747         // TS_FR10_04 consolelog destination set to realtive file
2748         /*18*/
2749         LoggingConfigFilenameErrorParams("[logger]\n"
2750                                          "sinks=consolelog\n"
2751                                          "[consolelog]\n"
2752                                          "destination=" ABS_PATH "\n",
2753                                          USER_LOGFILE_NAME, true,
2754                                          "Illegal destination"),
2755         // TS_FR10_05 consolelog destination set to realtive file
2756         /*19*/
2757         LoggingConfigFilenameErrorParams("[logger]\n"
2758                                          "sinks=consolelog\n"
2759                                          "[consolelog]\n"
2760                                          "destination=" ABS_DIR "\n",
2761                                          USER_LOGFILE_NAME, false,
2762                                          "Illegal destination"),
2763         // TS_FR04_05 absolute path in logger and legal filename fails
2764         /*20*/
2765         LoggingConfigFilenameErrorParams("[logger]\n"
2766                                          "sinks=filelog\n"
2767                                          "filename=" ABS_DIR "\n"
2768                                          "[filelog]\n"
2769                                          "filename=" USER_LOGFILE_NAME "\n",
2770                                          USER_LOGFILE_NAME, false,
2771                                          "must be a filename, not a path"),
2772         // TS_FR04_05a corner case
2773         /*21*/
2774         LoggingConfigFilenameErrorParams("[logger]\n"
2775                                          "sinks=filelog\n"
2776                                          "filename=/shouldfail.log\n"
2777                                          "[filelog]\n"
2778                                          "filename=" USER_LOGFILE_NAME "\n",
2779                                          USER_LOGFILE_NAME, false,
2780                                          "must be a filename, not a path"),
2781         // TS_FR04_06a corner case
2782         /*22*/
2783         LoggingConfigFilenameErrorParams("[logger]\n"
2784                                          "sinks=filelog\n"
2785                                          "filename=" USER_LOGFILE_NAME "\n"
2786                                          "[filelog]\n"
2787                                          "filename=/shouldfail.log\n",
2788                                          USER_LOGFILE_NAME, false,
2789                                          "is not a valid log filename")));
2790 
2791 struct LoggingConfigFilenameLoggingFolderParams {
2792   std::string logging_folder;
2793   std::string logger_config;
2794   std::string filename;
2795   bool catch_stderr;
2796   std::string expected_error;
2797 
LoggingConfigFilenameLoggingFolderParamsLoggingConfigFilenameLoggingFolderParams2798   LoggingConfigFilenameLoggingFolderParams(const std::string &logging_folder_,
2799                                            const std::string &logger_config_,
2800                                            const std::string &filename_,
2801                                            bool catch_stderr_,
2802                                            const std::string expected_error_)
2803       : logging_folder(logging_folder_),
2804         logger_config(logger_config_),
2805         filename(filename_),
2806         catch_stderr(catch_stderr_),
2807         expected_error(expected_error_) {}
2808 };
2809 
2810 class TempRelativeDirectory {
2811  public:
TempRelativeDirectory(const std::string & prefix="router")2812   explicit TempRelativeDirectory(const std::string &prefix = "router")
2813       : name_{get_tmp_dir_(prefix)} {}
2814 
~TempRelativeDirectory()2815   ~TempRelativeDirectory() { mysql_harness::delete_dir_recursive(name_); }
2816 
name() const2817   std::string name() const { return name_; }
2818 
2819  private:
2820   std::string name_;
2821 
2822 #ifndef WIN32
2823   // mysql_harness::get_tmp_dir() returns a relative path on these platforms
get_tmp_dir_(const std::string & name)2824   std::string get_tmp_dir_(const std::string &name) {
2825     return mysql_harness::get_tmp_dir(name);
2826   }
2827 #else
2828   // mysql_harness::get_tmp_dir() returns an abs path under GetTempPath() on
2829   // WIN32
get_tmp_dir_(const std::string & name)2830   std::string get_tmp_dir_(const std::string &name) {
2831     auto generate_random_sequence = [](size_t len) -> std::string {
2832       std::random_device rd;
2833       std::string result;
2834       static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
2835       std::uniform_int_distribution<unsigned long> dist(0,
2836                                                         sizeof(alphabet) - 2);
2837 
2838       for (size_t i = 0; i < len; ++i) {
2839         result += alphabet[dist(rd)];
2840       }
2841 
2842       return result;
2843     };
2844 
2845     std::string dir_name = name + "-" + generate_random_sequence(10);
2846     std::string result = Path(dir_name).str();
2847     int err = _mkdir(result.c_str());
2848     if (err != 0) {
2849       throw std::runtime_error("Error creating temporary directory " + result);
2850     }
2851     return result;
2852   }
2853 #endif
2854 };
2855 
2856 class RouterLoggingTestConfigFilenameLoggingFolder
2857     : public RouterLoggingTest,
2858       public ::testing::WithParamInterface<
2859           LoggingConfigFilenameLoggingFolderParams> {};
2860 
2861 /** @test This test verifies that consolelog destination may be set to various
2862  * devices
2863  */
TEST_P(RouterLoggingTestConfigFilenameLoggingFolder,LoggingTestFilenameLoggingFolder)2864 TEST_P(RouterLoggingTestConfigFilenameLoggingFolder,
2865        LoggingTestFilenameLoggingFolder) {
2866   auto test_params = GetParam();
2867 
2868   TempRelativeDirectory tmp_dir;
2869 
2870   // create the absolute path (note: order)
2871   Path abs_dir = Path(tmp_dir.name()).real_path();
2872   Path rel_dir = Path(tmp_dir.name()).basename();
2873 
2874   // Replace logging_folder tag with temporary directory
2875   std::string lf = test_params.logging_folder;
2876   while (lf.find(ABS_DIR) != std::string::npos) {
2877     lf.replace(lf.find(ABS_DIR), sizeof(ABS_DIR) - 1, abs_dir.c_str());
2878   }
2879   while (lf.find(REL_DIR) != std::string::npos) {
2880     lf.replace(lf.find(REL_DIR), sizeof(REL_DIR) - 1, rel_dir.c_str());
2881   }
2882 
2883   auto conf_params = get_DEFAULT_defaults();
2884   conf_params["logging_folder"] = lf;
2885 
2886   TempDirectory conf_dir("conf");
2887   const std::string cfg = "[routing]\n\n" + test_params.logger_config;
2888   const std::string conf_file =
2889       create_config_file(conf_dir.name(), cfg, &conf_params);
2890 
2891   // empty routing section gives failure while logging to defined sink
2892   auto &router = launch_router({"-c", conf_file}, 1, test_params.catch_stderr);
2893   check_exit_code(router, EXIT_FAILURE);
2894 
2895   const std::string console_log_txt = router.get_full_output();
2896   if (test_params.expected_error.empty()) {
2897     // expect something like this as error message on console/in log
2898     // 2020-03-19 10:00:00 main ERROR [7f539f628780] Configuration error: option
2899     // destinations in [routing] is required
2900     const std::string errmsg = "option destinations in [routing] is required";
2901 
2902     if (lf.empty()) {
2903       // log should go to consolelog, and contain routing error
2904       Path logfile = rel_dir.join(test_params.filename);
2905       EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n"
2906                                             << console_log_txt;
2907       EXPECT_FALSE(logfile.exists());
2908       EXPECT_THAT(console_log_txt, HasSubstr(errmsg)) << "\nconsole:\n"
2909                                                       << console_log_txt;
2910     } else {
2911       // log should go to logfile specified
2912       Path logfile = Path(lf).join(test_params.filename);
2913       EXPECT_TRUE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2914       EXPECT_TRUE(logfile.exists());
2915       std::string file_log_txt =
2916           router.get_full_logfile(test_params.filename, Path(lf).str());
2917       EXPECT_THAT(file_log_txt, HasSubstr(errmsg)) << "\nlog:\n"
2918                                                    << file_log_txt;
2919     }
2920   } else {
2921     // log should go to consolelog, and contain routing error
2922     EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2923     EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
2924         << "\nconsole:\n"
2925         << console_log_txt;
2926   }
2927 }
2928 
2929 INSTANTIATE_TEST_SUITE_P(
2930     LoggingTestConsoleDestinationDevices,
2931     RouterLoggingTestConfigFilenameLoggingFolder,
2932     ::testing::Values(
2933         // TS_FR03_01
2934         /*0*/
2935         LoggingConfigFilenameLoggingFolderParams("",
2936                                                  "[logger]\n"
2937                                                  "filename=" USER_LOGFILE_NAME
2938                                                  "\n",
2939                                                  USER_LOGFILE_NAME, true,
2940                                                  NOT_USED),
2941         // TS_FR03_02
2942         /*1*/
2943         LoggingConfigFilenameLoggingFolderParams(
2944             ABS_DIR, "[logger]\nfilename=" USER_LOGFILE_NAME "\n",
2945             USER_LOGFILE_NAME, false, NOT_USED),
2946         // TS_FR03_03
2947         /*2*/
2948         LoggingConfigFilenameLoggingFolderParams(
2949             REL_DIR, "[logger]\nfilename=" USER_LOGFILE_NAME "\n",
2950             USER_LOGFILE_NAME, false, NOT_USED),
2951         // TS_FR03_04
2952         /*3*/
2953         LoggingConfigFilenameLoggingFolderParams(
2954             "/non/existing/absolute/path/",
2955             "[logger]\nfilename=" USER_LOGFILE_NAME "\n", USER_LOGFILE_NAME,
2956             true, "Error when creating dir '/non/existing/absolute/path'"),
2957         // TS_FR03_05
2958         /*4*/
2959         LoggingConfigFilenameLoggingFolderParams(
2960             "non/existing/relative/path",
2961             "[logger]\nfilename=" USER_LOGFILE_NAME "\n", USER_LOGFILE_NAME,
2962             true, "Error when creating dir 'non/existing/relative/path'"),
2963         // TS_FR05_03 without [logger].filename
2964         // and TS_FR05_04 without [filesink].filename
2965         /*5*/
2966         LoggingConfigFilenameLoggingFolderParams(
2967             ABS_DIR, "[logger]\nsinks=filelog\n[filelog]\n",
2968             DEFAULT_LOGFILE_NAME, false, NOT_USED)));
2969 
2970 /** @test This test verifies that output goes to console when consolelog
2971  * destination is empty (TS_FR06_01)
2972  */
TEST_F(RouterLoggingTest,log_console_destination_empty)2973 TEST_F(RouterLoggingTest, log_console_destination_empty) {
2974   // FIXME: Unfortunately due to the limitations of our component testing
2975   // framework, this test has a flaw: it is not possible to distinguish if the
2976   // output returned from router.get_full_output() appeared on STDERR or STDOUT.
2977   // This should be fixed in the future.
2978   TempDirectory tmp_dir;
2979   auto conf_params = get_DEFAULT_defaults();
2980   conf_params["logging_folder"] = tmp_dir.name();
2981 
2982   TempDirectory conf_dir("conf");
2983   const std::string conf_text =
2984       "[routing]\n\n[logger]\nsinks=consolelog\n[consolelog]\ndestination=";
2985   const std::string conf_file =
2986       create_config_file(conf_dir.name(), conf_text, &conf_params);
2987 
2988   // empty routing section results in a failure, but while logging to
2989   // destination
2990   auto &router = launch_router({"-c", conf_file}, 1);
2991   check_exit_code(router, EXIT_FAILURE);
2992 
2993   // Expect the console log to be used on empty destinaton
2994   const std::string console_log_txt = router.get_full_output();
2995   EXPECT_FALSE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2996 
2997   // expect no default router file created in tmp_dir
2998   Path shouldnotexist = Path(tmp_dir.name()).join("mysqlrouter.log");
2999   EXPECT_FALSE(shouldnotexist.exists());
3000 }
3001 
3002 /** @test This test verifies that output to console does not contain a warning
3003  * or the userdefined logfile name when filename not in use (TS_FR08_01)
3004  */
TEST_F(RouterLoggingTest,log_console_unused_filename_no_warning)3005 TEST_F(RouterLoggingTest, log_console_unused_filename_no_warning) {
3006   // FIXME: Unfortunately due to the limitations of our component testing
3007   // framework, this test has a flaw: it is not possible to distinguish if the
3008   // output returned from router.get_full_output() appeared on STDERR or STDOUT.
3009   // This should be fixed in the future.
3010   TempDirectory tmp_dir;
3011   auto conf_params = get_DEFAULT_defaults();
3012   conf_params["logging_folder"] = tmp_dir.name();
3013 
3014   TempDirectory conf_dir("conf");
3015   const std::string conf_text =
3016       "[routing]\n\n[logger]\nfilename=" USER_LOGFILE_NAME
3017       "\nsinks=consolelog\n[consolelog]\n";
3018   const std::string conf_file =
3019       create_config_file(conf_dir.name(), conf_text, &conf_params);
3020 
3021   // empty routing section results in a failure, but while logging to
3022   // destination
3023   auto &router = launch_router({"-c", conf_file}, 1);
3024   check_exit_code(router, EXIT_FAILURE);
3025 
3026   // Expect the console log output to NOT contain warning or log file name
3027   const std::string console_log_txt = router.get_full_output();
3028   EXPECT_FALSE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
3029 
3030   EXPECT_THAT(console_log_txt, Not(HasSubstr(USER_LOGFILE_NAME)))
3031       << "\nconsole:\n"
3032       << console_log_txt;
3033 
3034   EXPECT_THAT(console_log_txt, Not(HasSubstr("warning"))) << "\nconsole:\n"
3035                                                           << console_log_txt;
3036 }
3037 
3038 /** @test This test verifies non-existing [consolelog].destination uses default
3039  * value. i.e console (TS_FR06_02)
3040  */
TEST_F(RouterLoggingTest,log_console_non_existing_destination)3041 TEST_F(RouterLoggingTest, log_console_non_existing_destination) {
3042   // FIXME: Unfortunately due to the limitations of our component testing
3043   // framework, this test has a flaw: it is not possible to distinguish if the
3044   // output returned from router.get_full_output() appeared on STDERR or STDOUT.
3045   // This should be fixed in the future.
3046   TempDirectory tmp_dir;
3047   auto conf_params = get_DEFAULT_defaults();
3048   conf_params["logging_folder"] = "";
3049 
3050   TempDirectory conf_dir("conf");
3051   const std::string conf_text =
3052       "[routing]\n\n[logger]\nsinks=consolelog\n[consolelog]\n";
3053   const std::string conf_file =
3054       create_config_file(conf_dir.name(), conf_text, &conf_params);
3055 
3056   // empty routing section results in a failure, but while logging to
3057   // destination
3058   auto &router = launch_router({"-c", conf_file}, 1);
3059   check_exit_code(router, EXIT_FAILURE);
3060 
3061   // Expect the console log output to NOT contain warning or log file name
3062   const std::string console_log_txt = router.get_full_output();
3063   EXPECT_FALSE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
3064 }
3065 
3066 #ifndef WIN32
3067 /** @test This test verifies that filename may be set to /dev/null the ugly way
3068  */
TEST_F(RouterLoggingTest,log_filename_dev_null_ugly)3069 TEST_F(RouterLoggingTest, log_filename_dev_null_ugly) {
3070   Path dev_null("/dev/null");
3071   EXPECT_TRUE(dev_null.exists());
3072 
3073   auto conf_params = get_DEFAULT_defaults();
3074   conf_params["logging_folder"] = "/dev";
3075 
3076   TempDirectory conf_dir("conf");
3077   const std::string conf_text = "[routing]\n\n[logger]\nfilename=null\n";
3078   const std::string conf_file =
3079       create_config_file(conf_dir.name(), conf_text, &conf_params);
3080 
3081   // empty routing section results in a failure, but while logging to file
3082   auto &router = launch_router({"-c", conf_file}, 1);
3083   check_exit_code(router, EXIT_FAILURE);
3084 
3085   // expect no default router file created in /dev
3086   Path shouldnotexist("/dev/mysqlrouter.log");
3087   EXPECT_FALSE(shouldnotexist.exists());
3088 
3089   EXPECT_TRUE(dev_null.exists());
3090 }
3091 #endif
3092 
main(int argc,char * argv[])3093 int main(int argc, char *argv[]) {
3094   init_windows_sockets();
3095   ProcessManager::set_origin(Path(argv[0]).dirname());
3096   ::testing::InitGoogleTest(&argc, argv);
3097   return RUN_ALL_TESTS();
3098 }
3099