/* Copyright 2013 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault #include "mongo/platform/basic.h" #include "mongo/config.h" #if defined(MONGO_CONFIG_HAVE_HEADER_UNISTD_H) #include #endif #ifndef _WIN32 #include #include #endif #include #include "mongo/bson/util/builder.h" #include "mongo/db/server_options.h" #include "mongo/db/server_options_helpers.h" #include "mongo/logger/logger.h" #include "mongo/unittest/unittest.h" #include "mongo/util/log.h" #include "mongo/util/options_parser/environment.h" #include "mongo/util/options_parser/option_section.h" #include "mongo/util/options_parser/options_parser.h" #include "mongo/util/scopeguard.h" namespace { using mongo::ErrorCodes; using mongo::Status; namespace moe = mongo::optionenvironment; class OptionsParserTester : public moe::OptionsParser { public: Status readConfigFile(const std::string& filename, std::string* config) { if (filename != _filename) { ::mongo::StringBuilder sb; sb << "Parser using filename: " << filename << " which does not match expected filename: " << _filename; return Status(ErrorCodes::InternalError, sb.str()); } *config = _config; return Status::OK(); } void setConfig(const std::string& filename, const std::string& config) { _filename = filename; _config = config; } private: std::string _filename; std::string _config; }; TEST(Verbosity, Default) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); std::map env_map; ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); // Make sure the log level didn't change since we didn't specify any verbose options ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Info()); } TEST(Verbosity, CommandLineImplicit) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--verbose"); std::map env_map; ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 1; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, CommandLineString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--verbose"); argv.push_back("vvvv"); std::map env_map; ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 4; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, CommandLineStringDisguisedLongForm) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("-verbose"); argv.push_back("vvvv"); std::map env_map; ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 4; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, CommandLineEmptyString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--verbose"); argv.push_back(""); std::map env_map; ASSERT_NOT_OK(parser.run(options, argv, env_map, &environment)); } TEST(Verbosity, CommandLineBadString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--verbose"); argv.push_back("beloud"); std::map env_map; ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_NOT_OK(::mongo::validateServerOptions(environment)); } TEST(Verbosity, CommandLineBadStringOnlyDash) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("-"); std::map env_map; ASSERT_NOT_OK(parser.run(options, argv, env_map, &environment)); } TEST(Verbosity, CommandLineBadStringOnlyTwoDashes) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--"); std::map env_map; ASSERT_OK(parser.run(options, argv, env_map, &environment)); } TEST(Verbosity, INIConfigString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--config"); argv.push_back("config.ini"); std::map env_map; parser.setConfig("config.ini", "verbose=vvvv"); ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 4; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, INIConfigBadString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--config"); argv.push_back("config.ini"); std::map env_map; parser.setConfig("config.ini", "verbose=beloud"); ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_NOT_OK(::mongo::validateServerOptions(environment)); } TEST(Verbosity, INIConfigEmptyString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--config"); argv.push_back("config.ini"); std::map env_map; parser.setConfig("config.ini", "verbose="); ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 0; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, JSONConfigString) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--config"); argv.push_back("config.json"); std::map env_map; parser.setConfig("config.json", "{ \"systemLog.verbosity\" : 4 }"); ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 4; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, MultipleSourcesMultipleOptions) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--config"); argv.push_back("config.json"); argv.push_back("--verbose"); argv.push_back("vvv"); std::map env_map; parser.setConfig("config.json", "{ \"systemLog.verbosity\" : 4 }"); ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); int verbosity = 3; ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); } TEST(Verbosity, YAMLConfigStringLogComponent) { OptionsParserTester parser; moe::Environment environment; moe::OptionSection options; // Reset the log level before we test ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogSeverity::Info()); // Log level for Storage will be cleared by config file value. ::mongo::logger::globalLogDomain()->setMinimumLoggedSeverity( ::mongo::logger::LogComponent::kStorage, ::mongo::logger::LogSeverity::Debug(1)); ASSERT_OK(::mongo::addGeneralServerOptions(&options)); std::vector argv; argv.push_back("binaryname"); argv.push_back("--config"); argv.push_back("config.yaml"); std::map env_map; parser.setConfig("config.yaml", "systemLog:\n" " verbosity: 4\n" " component:\n" " accessControl:\n" " verbosity: 0\n" " storage:\n" " verbosity: -1\n" " journal:\n" " verbosity: 2\n"); ASSERT_OK(parser.run(options, argv, env_map, &environment)); ASSERT_OK(::mongo::validateServerOptions(environment)); ASSERT_OK(::mongo::canonicalizeServerOptions(&environment)); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_OK(::mongo::storeServerOptions(environment)); // Verify component log levels using global log domain. int verbosity = 4; // Default ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity(), ::mongo::logger::LogSeverity::Debug(verbosity)); ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity( ::mongo::logger::LogComponent::kDefault), ::mongo::logger::LogSeverity::Debug(verbosity)); // AccessControl ASSERT_TRUE(::mongo::logger::globalLogDomain()->hasMinimumLogSeverity( ::mongo::logger::LogComponent::kAccessControl)); ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity( ::mongo::logger::LogComponent::kAccessControl), ::mongo::logger::LogSeverity::Log()); // Query - not mentioned in configuration. should match default. ASSERT_FALSE(::mongo::logger::globalLogDomain()->hasMinimumLogSeverity( ::mongo::logger::LogComponent::kStorage)); ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity( ::mongo::logger::LogComponent::kStorage), ::mongo::logger::LogSeverity::Debug(verbosity)); // Storage - cleared by -1 value in configuration. should match default. ASSERT_FALSE(::mongo::logger::globalLogDomain()->hasMinimumLogSeverity( ::mongo::logger::LogComponent::kStorage)); ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity( ::mongo::logger::LogComponent::kStorage), ::mongo::logger::LogSeverity::Debug(verbosity)); // Journaling - explicitly set to 2 in configuration. ASSERT_TRUE(::mongo::logger::globalLogDomain()->hasMinimumLogSeverity( ::mongo::logger::LogComponent::kJournal)); ASSERT_EQUALS(::mongo::logger::globalLogDomain()->getMinimumLogSeverity( ::mongo::logger::LogComponent::kJournal), ::mongo::logger::LogSeverity::Debug(2)); } TEST(SetupOptions, Default) { std::vector argv; argv.push_back("binaryname"); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_EQUALS(::mongo::serverGlobalParams.binaryName, "binaryname"); ASSERT_FALSE(::mongo::serverGlobalParams.cwd.empty()); ASSERT_EQUALS(::mongo::serverGlobalParams.argvArray.nFields(), static_cast(argv.size())); } TEST(SetupOptions, RelativeBinaryPath) { std::vector argv; argv.push_back("foo/bar/baz/binaryname"); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_EQUALS(::mongo::serverGlobalParams.binaryName, "binaryname"); ASSERT_FALSE(::mongo::serverGlobalParams.cwd.empty()); ASSERT_EQUALS(::mongo::serverGlobalParams.argvArray.nFields(), static_cast(argv.size())); } TEST(SetupOptions, AbsoluteBinaryPath) { std::vector argv; argv.push_back("/foo/bar/baz/binaryname"); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_EQUALS(::mongo::serverGlobalParams.binaryName, "binaryname"); ASSERT_FALSE(::mongo::serverGlobalParams.cwd.empty()); ASSERT_EQUALS(::mongo::serverGlobalParams.argvArray.nFields(), static_cast(argv.size())); } TEST(SetupOptions, EmptyBinaryName) { std::vector argv; argv.push_back(""); ASSERT_OK(::mongo::setupServerOptions(argv)); ASSERT_EQUALS(::mongo::serverGlobalParams.binaryName, ""); ASSERT_FALSE(::mongo::serverGlobalParams.cwd.empty()); ASSERT_EQUALS(::mongo::serverGlobalParams.argvArray.nFields(), static_cast(argv.size())); } TEST(SetupOptions, MissingBinaryName) { std::vector argv; ASSERT_NOT_OK(::mongo::setupServerOptions(argv)); } #if !defined(_WIN32) #define ASSERT_BOOST_SUCCESS(ec) ASSERT_FALSE(ec) << ec.message() TEST(SetupOptions, DeepCwd) { std::vector argv; argv.push_back("binaryname"); // Save the original cwd. boost::system::error_code ec; boost::filesystem::path cwd = boost::filesystem::current_path(ec); ASSERT_BOOST_SUCCESS(ec); // All work is done under a temporary base directory. ::mongo::StringBuilder sb; sb << "/tmp/deepcwd-" << getpid(); boost::filesystem::path deepBaseDir = sb.str(); auto cleanup = ::mongo::MakeGuard([&] { boost::filesystem::current_path(cwd, ec); boost::filesystem::remove_all(deepBaseDir, ec); }); // Clear out any old base dir, and create an empty dir. boost::filesystem::remove_all(deepBaseDir, ec); ASSERT_BOOST_SUCCESS(ec); boost::filesystem::create_directories(deepBaseDir, ec); ASSERT_BOOST_SUCCESS(ec); // Resolve the canonical base dir, in case /tmp is a symlink (eg. on OSX). // This involves chdir into it, then getcwd() again (since getcwd() returns a canonical path). boost::filesystem::current_path(deepBaseDir, ec); ASSERT_BOOST_SUCCESS(ec); deepBaseDir = boost::filesystem::current_path(ec); ASSERT_BOOST_SUCCESS(ec); boost::filesystem::path deepCwd = deepBaseDir; boost::filesystem::path pathComponent = std::string(40, 'a'); while (deepCwd.size() < 3800) { if (!boost::filesystem::create_directory(deepCwd / pathComponent, ec)) { // Could not mkdir, so we are done and will stick with the previous level, thanks. break; } boost::filesystem::current_path(deepCwd / pathComponent, ec); if (ec) { // Could not chdir, so we are done and will stick with the previous level, thanks. break; } deepCwd /= pathComponent; } // Run the actual test. Status res = ::mongo::setupServerOptions(argv); ASSERT_OK(res); ASSERT_FALSE(::mongo::serverGlobalParams.cwd.empty()); ASSERT_EQUALS(::mongo::serverGlobalParams.cwd, deepCwd.native()) << "serverGlobalParams.cwd is " << ::mongo::serverGlobalParams.cwd.size() << " bytes long and deepCwd is " << deepCwd.size() << " bytes long."; ASSERT_EQUALS(::mongo::serverGlobalParams.argvArray.nFields(), static_cast(argv.size())); } TEST(SetupOptions, UnlinkedCwd) { std::vector argv; argv.push_back("binaryname"); // Save the original cwd. boost::system::error_code ec; boost::filesystem::path cwd = boost::filesystem::current_path(ec); ASSERT_BOOST_SUCCESS(ec); std::string unlinkDir; auto cleanup = ::mongo::MakeGuard([&] { boost::filesystem::current_path(cwd, ec); if (!unlinkDir.empty()) { boost::filesystem::remove(cwd / unlinkDir, ec); } }); // mkdir our own unlink dir unsigned int i = 0; while (1) { ::mongo::StringBuilder sb; sb << "unlink" << i; unlinkDir = sb.str(); if (boost::filesystem::create_directory(unlinkDir, ec)) { // New directory was created, great. break; } else { // Directory failed to create, or already existed (so try the next one). ASSERT_BOOST_SUCCESS(ec); i++; } } // chdir into the unlink dir boost::filesystem::current_path(unlinkDir, ec); ASSERT_BOOST_SUCCESS(ec); // Naive "rmdir ." doesn't work on Linux. // Naive rmdir of cwd doesn't work on Solaris doesn't work (no matter how it's specified). // So we use a subprocess to unlink the dir. pid_t pid = fork(); ASSERT_NOT_EQUALS(pid, -1) << "unable to fork: " << ::mongo::errnoWithDescription(); if (pid == 0) { // Subprocess // No exceptions, ASSERT(), FAIL() or logging. // Only signalling to the parent process via exit status. // Only quickexit via _exit() (which prevents the cleanup guard from running, which is // what we want). // cd to root boost::filesystem::current_path("/", ec); if (ec) { _exit(1); } // rmdir using absolute path boost::filesystem::remove(cwd / unlinkDir, ec); if (ec) { _exit(2); } _exit(0); } // Wait for the subprocess to finish running the above code and exit, and check its result. int status; ASSERT_NOT_EQUALS(-1, waitpid(pid, &status, 0)); ASSERT(WIFEXITED(status)); int exitstatus = WEXITSTATUS(status); if (exitstatus == 1) { FAIL("subprocess was unable to cd to /"); } else if (exitstatus == 2) { FAIL("subprocess was unable to remove unlink dir"); } else { ASSERT_EQUALS(0, exitstatus); } // Run the actual test. Status res = ::mongo::setupServerOptions(argv); ASSERT_NOT_OK(res); ASSERT_STRING_CONTAINS(res.toString(), "Cannot get current working directory"); ASSERT_STRING_CONTAINS(res.toString(), "No such file or directory"); // message for ENOENT } #endif // !defined(_WIN32) } // unnamed namespace