From c59a956f66c7f7887276ac4be8e62281c60f4cad Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Fri, 5 Oct 2018 15:36:58 +0200 Subject: [PATCH] Improve urd's command line argument handling Fixes #14. Closes #9. --- src/Makefile.am | 5 +- src/config.hpp | 1 + src/config/defaults.hpp | 1 + src/config/settings.cpp | 29 +++++-- src/config/settings.hpp | 33 ++++--- src/fmt.hpp | 33 +++++++ src/main.cpp | 184 +++++++++++++++++++++++++--------------- src/urd.cpp | 22 +++-- tests/fake-daemon.cpp | 1 + 9 files changed, 218 insertions(+), 91 deletions(-) create mode 100644 src/fmt.hpp diff --git a/src/Makefile.am b/src/Makefile.am index c939cdc..e994b4a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -216,8 +216,9 @@ config/defaults.cpp: Makefile echo "namespace config {"; \ echo "namespace defaults {"; \ echo " const char* progname = \"urd\";"; \ - echo " const bool daemonize = true;"; \ - echo " const bool use_syslog = false;"; \ + echo " const bool daemonize = true;"; \ + echo " const bool use_syslog = false;"; \ + echo " const bool use_console = false;"; \ echo " const bfs::path log_file = boost::filesystem::path();"; \ echo " const uint32_t log_file_max_size = static_cast(16*1024*1024);"; \ echo ""; \ diff --git a/src/config.hpp b/src/config.hpp index 0b71d8f..bd17612 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -28,6 +28,7 @@ #ifndef __NORNS_CONFIG_HPP__ #define __NORNS_CONFIG_HPP__ +#include "config.h" #include "config/defaults.hpp" #include "config/settings.hpp" diff --git a/src/config/defaults.hpp b/src/config/defaults.hpp index 41de6fb..1a38ec6 100644 --- a/src/config/defaults.hpp +++ b/src/config/defaults.hpp @@ -41,6 +41,7 @@ namespace defaults { extern const char* progname; extern const bool daemonize; extern const bool use_syslog; + extern const bool use_console; extern const bfs::path log_file; extern const uint32_t log_file_max_size; extern const bool dry_run; diff --git a/src/config/settings.cpp b/src/config/settings.cpp index b176f26..accbf0f 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -45,17 +45,27 @@ namespace norns { namespace config { settings::settings() { } -settings::settings(const std::string& progname, bool daemonize, bool use_syslog, - const bfs::path& log_file, const uint32_t log_file_max_size, - bool dry_run, uint32_t dry_run_duration, + +settings::settings(const std::string& progname, + bool daemonize, + bool use_syslog, + bool use_console, + const bfs::path& log_file, + const uint32_t log_file_max_size, + bool dry_run, + uint32_t dry_run_duration, const bfs::path& global_socket, - const bfs::path& control_socket, uint32_t remote_port, - const bfs::path& pidfile, uint32_t workers, - uint32_t backlog_size, const bfs::path& cfgfile, + const bfs::path& control_socket, + uint32_t remote_port, + const bfs::path& pidfile, + uint32_t workers, + uint32_t backlog_size, + const bfs::path& cfgfile, const std::list& defns) : m_progname(progname), m_daemonize(daemonize), m_use_syslog(use_syslog), + m_use_console(use_console), m_log_file(log_file), m_log_file_max_size(log_file_max_size), m_dry_run(dry_run), @@ -73,6 +83,7 @@ void settings::load_defaults() { m_progname = defaults::progname; m_daemonize = defaults::daemonize; m_use_syslog = defaults::use_syslog; + m_use_console = defaults::use_console; m_log_file = defaults::log_file; m_log_file_max_size = defaults::log_file_max_size; m_dry_run = defaults::dry_run; @@ -98,6 +109,7 @@ void settings::load_from_file(const bfs::path& filename) { m_progname = defaults::progname; m_use_syslog = gsettings.get_as(keywords::use_syslog); + m_use_console = defaults::use_console; if(gsettings.has(keywords::log_file)) { m_log_file = gsettings.get_as(keywords::log_file); @@ -137,6 +149,7 @@ std::string settings::to_string() const { " m_progname: " + m_progname + ",\n" + " m_daemonize: " + (m_daemonize ? "true" : "false") + ",\n" + " m_use_syslog: " + (m_use_syslog ? "true" : "false") + ",\n" + + " m_use_console: " + (m_use_console ? "true" : "false") + ",\n" + " m_log_file: " + m_log_file.string() + ",\n" + " m_log_file_max_size: " + std::to_string(m_log_file_max_size) + ",\n" + " m_dry_run: " + (m_dry_run ? "true" : "false") + ",\n" + @@ -165,6 +178,10 @@ bool& settings::use_syslog() { return m_use_syslog; } +bool& settings::use_console() { + return m_use_console; +} + bfs::path& settings::log_file() { return m_log_file; } diff --git a/src/config/settings.hpp b/src/config/settings.hpp index 64370af..309a515 100644 --- a/src/config/settings.hpp +++ b/src/config/settings.hpp @@ -39,9 +39,12 @@ namespace config { struct namespace_def { - namespace_def(const std::string& nsid, bool track, - const bfs::path& mountpoint, const std::string& alias, - const uint64_t capacity, const std::string& visibility) : + namespace_def(const std::string& nsid, + bool track, + const bfs::path& mountpoint, + const std::string& alias, + const uint64_t capacity, + const std::string& visibility) : m_nsid(nsid), m_track(track), m_mountpoint(mountpoint), @@ -84,13 +87,21 @@ struct namespace_def { struct settings { settings(); - settings(const std::string& progname, bool daemonize, bool use_syslog, - const bfs::path& log_file, const uint32_t log_file_max_size, - bool dry_run, uint32_t dry_run_duration, - const bfs::path& global_socket, - const bfs::path& control_socket, uint32_t remote_port, - const bfs::path& pidfile, uint32_t workers, - uint32_t backlog_size, const bfs::path& cfgfile, + settings(const std::string& progname, + bool daemonize, + bool use_syslog, + bool use_console, + const bfs::path& log_file, + const uint32_t log_file_max_size, + bool dry_run, + uint32_t dry_run_duration, + const bfs::path& global_socket, + const bfs::path& control_socket, + uint32_t remote_port, + const bfs::path& pidfile, + uint32_t workers, + uint32_t backlog_size, + const bfs::path& cfgfile, const std::list& defns); void load_defaults(); void load_from_file(const bfs::path& filename); @@ -99,6 +110,7 @@ struct settings { std::string& progname(); bool& daemonize(); bool& use_syslog(); + bool& use_console(); bfs::path& log_file(); uint32_t& log_file_max_size(); bool& dry_run(); @@ -115,6 +127,7 @@ struct settings { std::string m_progname = defaults::progname; bool m_daemonize = defaults::daemonize; bool m_use_syslog = defaults::use_syslog; + bool m_use_console = defaults::use_console; bfs::path m_log_file = defaults::log_file; uint32_t m_log_file_max_size = defaults::log_file_max_size; bool m_dry_run = defaults::dry_run; diff --git a/src/fmt.hpp b/src/fmt.hpp new file mode 100644 index 0000000..9bc572d --- /dev/null +++ b/src/fmt.hpp @@ -0,0 +1,33 @@ +/************************************************************************* + * copyright (c) 2017-2018 barcelona supercomputing center * + * centro nacional de supercomputacion * + * all rights reserved. * + * * + * this file is part of the norns data scheduler, a service that allows * + * other programs to start, track and manage asynchronous transfers of * + * data resources transfers requests between different storage backends. * + * * + * see authors file in the top level directory for information * + * regarding developers and contributors. * + * * + * the norns data scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the gnu lesser general public * + * license as published by the free software foundation, either * + * version 3 of the license, or (at your option) any later version. * + * * + * the norns data scheduler 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 * + * lesser general public license for more details. * + * * + * you should have received a copy of the gnu lesser general * + * public license along with the norns data scheduler. if not, see * + * . * + *************************************************************************/ + +#ifndef __URD_FMT_HPP__ +#define __URD_FMT_HPP__ + +#include "spdlog/fmt/fmt.h" + +#endif /* __URD_FMT_HPP__ */ diff --git a/src/main.cpp b/src/main.cpp index 90a2d3b..b8e30f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,27 +1,27 @@ /************************************************************************* - * Copyright (C) 2017-2018 Barcelona Supercomputing Center * - * Centro Nacional de Supercomputacion * - * All rights reserved. * + * copyright (c) 2017-2018 barcelona supercomputing center * + * centro nacional de supercomputacion * + * all rights reserved. * * * - * This file is part of the NORNS Data Scheduler, a service that allows * + * this file is part of the norns data scheduler, a service that allows * * other programs to start, track and manage asynchronous transfers of * * data resources transfers requests between different storage backends. * * * - * See AUTHORS file in the top level directory for information * + * see authors file in the top level directory for information * * regarding developers and contributors. * * * - * The NORNS Data Scheduler is free software: you can redistribute it * - * and/or modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation, either * - * version 3 of the License, or (at your option) any later version. * + * the norns data scheduler is free software: you can redistribute it * + * and/or modify it under the terms of the gnu lesser general public * + * license as published by the free software foundation, either * + * version 3 of the license, or (at your option) any later version. * * * - * The NORNS Data Scheduler 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 * - * Lesser General Public License for more details. * + * the norns data scheduler 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 * + * lesser general public license for more details. * * * - * You should have received a copy of the GNU Lesser General * - * Public License along with the NORNS Data Scheduler. If not, see * + * you should have received a copy of the gnu lesser general * + * public license along with the norns data scheduler. if not, see * * . * *************************************************************************/ @@ -31,86 +31,136 @@ #include #include "config.hpp" #include "urd.hpp" +#include "fmt.hpp" namespace bfs = boost::filesystem; namespace bpo = boost::program_options; -namespace { - -void -option_dependency(const boost::program_options::variables_map& vm, - const std::string& for_what, - const std::string& required_option) { - - if(vm.count(for_what) && !vm[for_what].defaulted()) { - if(vm.count(required_option) == 0 || vm[required_option].defaulted()) { - throw std::logic_error(std::string("Option '") + for_what + - "' requires option '" + required_option + "'."); - } - } +void +print_version(const std::string& progname) { + fmt::print("{} {}\n", progname, NORNS_VERSION); } +void +print_help(const std::string& progname, + const bpo::options_description& opt_desc) { + fmt::print("Usage: {} [options]\n\n", progname); + fmt::print("{}", opt_desc); } -int main(int argc, char* argv[]){ +int +main(int argc, char* argv[]) { norns::config::settings cfg; cfg.load_defaults(); - bool run_in_foreground = !cfg.daemonize(); - bool dry_run = cfg.dry_run(); - uint32_t dry_run_duration = cfg.dry_run_duration(); - - // declare a group of options that will be allowed only on the command line - bpo::options_description generic("Allowed options"); - generic.add_options() + // define the command line options allowed + bpo::options_description opt_desc("Options"); + opt_desc.add_options() + // run in foreground (",f", - bpo::bool_switch(&run_in_foreground), + bpo::bool_switch() + ->default_value(false) + ->notifier( + [&](const bool& flag_value) { + cfg.daemonize() = !flag_value; + }), "foreground operation") + + // noop tasks + duration ("dry-run,d", - bpo::value()->value_name("N")->implicit_value(100), - "don't actually execute tasks, but wait N microseconds per task if an argument is provided") + bpo::value() + ->value_name("N") + ->implicit_value(100) + ->notifier( + [&](const uint32_t& duration_value) { + cfg.dry_run() = true; + cfg.dry_run_duration() = duration_value; + }), + "don't actually execute tasks, but wait N microseconds per task if " + "an argument is provided") + + // force logging messages to the console + ("force-console,C", + bpo::value() + ->implicit_value("") + ->zero_tokens() + ->notifier( + [&](const std::string&) { + cfg.use_console() = true; + }), + "override any logging options defined in configuration files and " + "send all daemon output to the console" + ) + + // print the daemon version ("version,v", + bpo::value() + ->implicit_value("") + ->zero_tokens(), "print version string") + + // print help ("help,h", + bpo::value() + ->implicit_value("") + ->zero_tokens(), "produce help message") ; - // declare a group of options that will be allowed in a config file + // parse the command line bpo::variables_map vm; - bpo::store(bpo::parse_command_line(argc, argv, generic), vm); - bpo::notify(vm); - if (vm.count("help")) { - std::cout << generic << "\n"; - exit(EXIT_SUCCESS); - } + try { + bpo::store(bpo::parse_command_line(argc, argv, opt_desc), vm); - if(!bfs::exists(cfg.config_file())) { - std::cerr << "Failed to access service configuration file " << cfg.config_file() << "\n"; - exit(EXIT_FAILURE); + // the --help and --version arguments are special, since we want + // to process them even if the global configuration file doesn't exist + if(vm.count("help")) { + print_help(cfg.progname(), opt_desc); + return EXIT_SUCCESS; + } + + if(vm.count("version")) { + print_version(cfg.progname()); + return EXIT_SUCCESS; + } + + if(!bfs::exists(cfg.config_file())) { + fmt::print(stderr, "Failed to access daemon configuration file {}\n", + cfg.config_file()); + return EXIT_FAILURE; + } + + try { + cfg.load_from_file(cfg.config_file()); + } + catch(const std::exception& ex) { + fmt::print(stderr, "Failed reading daemon configuration file:\n" + " {}\n", ex.what()); + return EXIT_FAILURE; + } + + // calling notify() here basically invokes all define notifiers, thus + // overridingany configuration loaded from the global configuration file + // with its command-line counterparts if provided (for those options where + // this is available) + bpo::notify(vm); + } + catch(const bpo::error& ex) { + fmt::print(stderr, "ERROR: {}\n\n", ex.what()); + return EXIT_FAILURE; } try { - cfg.load_from_file(cfg.config_file()); + norns::urd daemon; + daemon.configure(cfg); + return daemon.run(); } catch(const std::exception& ex) { - std::cerr << "Error reading config file:\n " << ex.what() << "\n"; - exit(EXIT_FAILURE); - } - - if(vm.count("dry-run")) { - dry_run = true; - dry_run_duration = vm["dry-run"].as(); + fmt::print(stderr, "An unhandled exception reached the top of main(), " + "{} will exit:\n what(): {}\n", + cfg.progname(), ex.what()); + return EXIT_FAILURE; } - - // override settings from file with command-line arguments - cfg.daemonize() = !run_in_foreground; - cfg.dry_run() = dry_run; - cfg.dry_run_duration() = dry_run_duration; - - norns::urd daemon; - daemon.configure(cfg); - - return daemon.run(); } diff --git a/src/urd.cpp b/src/urd.cpp index eee815a..e608360 100644 --- a/src/urd.cpp +++ b/src/urd.cpp @@ -53,7 +53,7 @@ #include "resources.hpp" #include "io.hpp" #include "namespaces.hpp" - +#include "fmt.hpp" #include "urd.hpp" namespace norns { @@ -654,20 +654,30 @@ void urd::signal_handler(int signum){ } void urd::init_logger() { + + if(m_settings->use_console()) { + logger::create_global_logger(m_settings->progname(), "console color"); + return;; + } + if(m_settings->use_syslog()) { logger::create_global_logger(m_settings->progname(), "syslog"); if(!m_settings->daemonize()) { - std::cerr << "WARNING: Output messages redirected to syslog\n"; + fmt::print(stderr, "PSA: Output sent to syslog while in " + "non-daemon mode\n"); } + + return; } - else if(!m_settings->log_file().empty()) { + + if(!m_settings->log_file().empty()) { logger::create_global_logger(m_settings->progname(), "file", m_settings->log_file()); + return; } - else { - logger::create_global_logger(m_settings->progname(), "console color"); - } + + logger::create_global_logger(m_settings->progname(), "console color"); } void urd::init_event_handlers() { diff --git a/tests/fake-daemon.cpp b/tests/fake-daemon.cpp index 1d710c2..def2477 100644 --- a/tests/fake-daemon.cpp +++ b/tests/fake-daemon.cpp @@ -38,6 +38,7 @@ norns::config::settings test_cfg( "test_urd", /* progname */ false, /* daemonize */ false, /* use syslog */ + false, /* use console */ {},// "./test_urd.log", /* log file */ 0, /* unused */ false, /* dry run */ -- GitLab