#include "server/options_impl.h" #include #include #include #include #include "envoy/admin/v3/server_info.pb.h" #include "common/common/fmt.h" #include "common/common/logger.h" #include "common/common/macros.h" #include "common/protobuf/utility.h" #include "common/version/version.h" #include "server/options_impl_platform.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "spdlog/spdlog.h" #include "tclap/CmdLine.h" namespace Envoy { namespace { std::vector toArgsVector(int argc, const char* const* argv) { std::vector args; args.reserve(argc); for (int i = 0; i < argc; ++i) { args.emplace_back(argv[i]); } return args; } } // namespace OptionsImpl::OptionsImpl(int argc, const char* const* argv, const HotRestartVersionCb& hot_restart_version_cb, spdlog::level::level_enum default_log_level) : OptionsImpl(toArgsVector(argc, argv), hot_restart_version_cb, default_log_level) {} OptionsImpl::OptionsImpl(std::vector args, const HotRestartVersionCb& hot_restart_version_cb, spdlog::level::level_enum default_log_level) : signal_handling_enabled_(true) { std::string log_levels_string = fmt::format("Log levels: {}", allowedLogLevels()); log_levels_string += fmt::format("\nDefault is [{}]", spdlog::level::level_string_views[default_log_level]); const std::string component_log_level_string = "Comma separated list of component log levels. For example upstream:debug,config:trace"; const std::string log_format_string = fmt::format("Log message format in spdlog syntax " "(see https://github.com/gabime/spdlog/wiki/3.-Custom-formatting)" "\nDefault is \"{}\"", Logger::Logger::DEFAULT_LOG_FORMAT); TCLAP::CmdLine cmd("envoy", ' ', VersionInfo::version()); TCLAP::ValueArg base_id( "", "base-id", "base ID so that multiple envoys can run on the same host if needed", false, 0, "uint32_t", cmd); TCLAP::SwitchArg use_dynamic_base_id( "", "use-dynamic-base-id", "the server chooses a base ID dynamically. Supersedes a static base ID. May not be used " "when the restart epoch is non-zero.", cmd, false); TCLAP::ValueArg base_id_path( "", "base-id-path", "path to which the base ID is written", false, "", "string", cmd); TCLAP::ValueArg concurrency("", "concurrency", "# of worker threads to run", false, std::thread::hardware_concurrency(), "uint32_t", cmd); TCLAP::ValueArg config_path("c", "config-path", "Path to configuration file", false, "", "string", cmd); TCLAP::ValueArg config_yaml( "", "config-yaml", "Inline YAML configuration, merges with the contents of --config-path", false, "", "string", cmd); TCLAP::ValueArg bootstrap_version( "", "bootstrap-version", "API version to parse the bootstrap config as (e.g. 3). If " "unset, all known versions will be attempted", false, 0, "string", cmd); TCLAP::SwitchArg allow_unknown_fields("", "allow-unknown-fields", "allow unknown fields in static configuration (DEPRECATED)", cmd, false); TCLAP::SwitchArg allow_unknown_static_fields("", "allow-unknown-static-fields", "allow unknown fields in static configuration", cmd, false); TCLAP::SwitchArg reject_unknown_dynamic_fields("", "reject-unknown-dynamic-fields", "reject unknown fields in dynamic configuration", cmd, false); TCLAP::SwitchArg ignore_unknown_dynamic_fields("", "ignore-unknown-dynamic-fields", "ignore unknown fields in dynamic configuration", cmd, false); TCLAP::ValueArg admin_address_path("", "admin-address-path", "Admin address path", false, "", "string", cmd); TCLAP::ValueArg local_address_ip_version("", "local-address-ip-version", "The local " "IP address version (v4 or v6).", false, "v4", "string", cmd); TCLAP::ValueArg log_level( "l", "log-level", log_levels_string, false, spdlog::level::level_string_views[default_log_level].data(), "string", cmd); TCLAP::ValueArg component_log_level( "", "component-log-level", component_log_level_string, false, "", "string", cmd); TCLAP::ValueArg log_format("", "log-format", log_format_string, false, Logger::Logger::DEFAULT_LOG_FORMAT, "string", cmd); TCLAP::SwitchArg log_format_escaped("", "log-format-escaped", "Escape c-style escape sequences in the application logs", cmd, false); TCLAP::SwitchArg enable_fine_grain_logging( "", "enable-fine-grain-logging", "Logger mode: enable file level log control(Fancy Logger)or not", cmd, false); TCLAP::ValueArg log_path("", "log-path", "Path to logfile", false, "", "string", cmd); TCLAP::ValueArg restart_epoch("", "restart-epoch", "hot restart epoch #", false, 0, "uint32_t", cmd); TCLAP::SwitchArg hot_restart_version_option("", "hot-restart-version", "hot restart compatibility version", cmd); TCLAP::ValueArg service_cluster("", "service-cluster", "Cluster name", false, "", "string", cmd); TCLAP::ValueArg service_node("", "service-node", "Node name", false, "", "string", cmd); TCLAP::ValueArg service_zone("", "service-zone", "Zone name", false, "", "string", cmd); TCLAP::ValueArg file_flush_interval_msec("", "file-flush-interval-msec", "Interval for log flushing in msec", false, 10000, "uint32_t", cmd); TCLAP::ValueArg drain_time_s("", "drain-time-s", "Hot restart and LDS removal drain time in seconds", false, 600, "uint32_t", cmd); TCLAP::ValueArg drain_strategy( "", "drain-strategy", "Hot restart drain sequence behaviour, one of 'gradual' (default) or 'immediate'.", false, "gradual", "string", cmd); TCLAP::ValueArg parent_shutdown_time_s("", "parent-shutdown-time-s", "Hot restart parent shutdown time in seconds", false, 900, "uint32_t", cmd); TCLAP::ValueArg mode("", "mode", "One of 'serve' (default; validate configs and then serve " "traffic normally) or 'validate' (validate configs and exit).", false, "serve", "string", cmd); TCLAP::SwitchArg disable_hot_restart("", "disable-hot-restart", "Disable hot restart functionality", cmd, false); TCLAP::SwitchArg enable_mutex_tracing( "", "enable-mutex-tracing", "Enable mutex contention tracing functionality", cmd, false); TCLAP::SwitchArg cpuset_threads( "", "cpuset-threads", "Get the default # of worker threads from cpuset size", cmd, false); TCLAP::ValueArg disable_extensions("", "disable-extensions", "Comma-separated list of extensions to disable", false, "", "string", cmd); TCLAP::ValueArg socket_path("", "socket-path", "Path to hot restart socket file", false, "@envoy_domain_socket", "string", cmd); TCLAP::ValueArg socket_mode("", "socket-mode", "Socket file permission", false, "600", "string", cmd); cmd.setExceptionHandling(false); try { cmd.parse(args); count_ = cmd.getArgList().size(); } catch (TCLAP::ArgException& e) { try { cmd.getOutput()->failure(cmd, e); } catch (const TCLAP::ExitException&) { // failure() has already written an informative message to stderr, so all that's left to do // is throw our own exception with the original message. throw MalformedArgvException(e.what()); } } catch (const TCLAP::ExitException& e) { // parse() throws an ExitException with status 0 after printing the output for --help and // --version. throw NoServingException(); } hot_restart_disabled_ = disable_hot_restart.getValue(); mutex_tracing_enabled_ = enable_mutex_tracing.getValue(); cpuset_threads_ = cpuset_threads.getValue(); if (log_level.isSet()) { log_level_ = parseAndValidateLogLevel(log_level.getValue()); } else { log_level_ = default_log_level; } log_format_ = log_format.getValue(); log_format_escaped_ = log_format_escaped.getValue(); enable_fine_grain_logging_ = enable_fine_grain_logging.getValue(); parseComponentLogLevels(component_log_level.getValue()); if (mode.getValue() == "serve") { mode_ = Server::Mode::Serve; } else if (mode.getValue() == "validate") { mode_ = Server::Mode::Validate; } else if (mode.getValue() == "init_only") { mode_ = Server::Mode::InitOnly; } else { const std::string message = fmt::format("error: unknown mode '{}'", mode.getValue()); throw MalformedArgvException(message); } if (local_address_ip_version.getValue() == "v4") { local_address_ip_version_ = Network::Address::IpVersion::v4; } else if (local_address_ip_version.getValue() == "v6") { local_address_ip_version_ = Network::Address::IpVersion::v6; } else { const std::string message = fmt::format("error: unknown IP address version '{}'", local_address_ip_version.getValue()); throw MalformedArgvException(message); } base_id_ = base_id.getValue(); use_dynamic_base_id_ = use_dynamic_base_id.getValue(); base_id_path_ = base_id_path.getValue(); restart_epoch_ = restart_epoch.getValue(); if (use_dynamic_base_id_ && restart_epoch_ > 0) { const std::string message = fmt::format( "error: cannot use --restart-epoch={} with --use-dynamic-base-id", restart_epoch_); throw MalformedArgvException(message); } if (!concurrency.isSet() && cpuset_threads_) { // The 'concurrency' command line option wasn't set but the 'cpuset-threads' // option was set. Use the number of CPUs assigned to the process cpuset, if // that can be known. concurrency_ = OptionsImplPlatform::getCpuCount(); } else { if (concurrency.isSet() && cpuset_threads_ && cpuset_threads.isSet()) { ENVOY_LOG(warn, "Both --concurrency and --cpuset-threads options are set; not applying " "--cpuset-threads."); } concurrency_ = std::max(1U, concurrency.getValue()); } config_path_ = config_path.getValue(); config_yaml_ = config_yaml.getValue(); if (bootstrap_version.getValue() != 0) { bootstrap_version_ = bootstrap_version.getValue(); } if (allow_unknown_fields.getValue()) { ENVOY_LOG(warn, "--allow-unknown-fields is deprecated, use --allow-unknown-static-fields instead."); } allow_unknown_static_fields_ = allow_unknown_static_fields.getValue() || allow_unknown_fields.getValue(); reject_unknown_dynamic_fields_ = reject_unknown_dynamic_fields.getValue(); ignore_unknown_dynamic_fields_ = ignore_unknown_dynamic_fields.getValue(); admin_address_path_ = admin_address_path.getValue(); log_path_ = log_path.getValue(); service_cluster_ = service_cluster.getValue(); service_node_ = service_node.getValue(); service_zone_ = service_zone.getValue(); file_flush_interval_msec_ = std::chrono::milliseconds(file_flush_interval_msec.getValue()); drain_time_ = std::chrono::seconds(drain_time_s.getValue()); parent_shutdown_time_ = std::chrono::seconds(parent_shutdown_time_s.getValue()); socket_path_ = socket_path.getValue(); if (socket_path_.at(0) == '@') { socket_mode_ = 0; } else { uint64_t socket_mode_helper; if (!StringUtil::atoull(socket_mode.getValue().c_str(), socket_mode_helper, 8)) { throw MalformedArgvException( fmt::format("error: invalid socket-mode '{}'", socket_mode.getValue())); } socket_mode_ = socket_mode_helper; } if (drain_strategy.getValue() == "immediate") { drain_strategy_ = Server::DrainStrategy::Immediate; } else if (drain_strategy.getValue() == "gradual") { drain_strategy_ = Server::DrainStrategy::Gradual; } else { throw MalformedArgvException( fmt::format("error: unknown drain-strategy '{}'", mode.getValue())); } if (hot_restart_version_option.getValue()) { std::cerr << hot_restart_version_cb(!hot_restart_disabled_); throw NoServingException(); } if (!disable_extensions.getValue().empty()) { disabled_extensions_ = absl::StrSplit(disable_extensions.getValue(), ','); } } spdlog::level::level_enum OptionsImpl::parseAndValidateLogLevel(absl::string_view log_level) { if (log_level == "warn") { return spdlog::level::level_enum::warn; } size_t level_to_use = std::numeric_limits::max(); for (size_t i = 0; i < ARRAY_SIZE(spdlog::level::level_string_views); i++) { spdlog::string_view_t spd_log_level = spdlog::level::level_string_views[i]; if (log_level == absl::string_view(spd_log_level.data(), spd_log_level.size())) { level_to_use = i; break; } } if (level_to_use == std::numeric_limits::max()) { logError(fmt::format("error: invalid log level specified '{}'", log_level)); } return static_cast(level_to_use); } std::string OptionsImpl::allowedLogLevels() { std::string allowed_log_levels; for (auto level_string_view : spdlog::level::level_string_views) { if (level_string_view == spdlog::level::to_string_view(spdlog::level::warn)) { allowed_log_levels += fmt::format("[{}|warn]", level_string_view); } else { allowed_log_levels += fmt::format("[{}]", level_string_view); } } return allowed_log_levels; } void OptionsImpl::parseComponentLogLevels(const std::string& component_log_levels) { if (component_log_levels.empty()) { return; } component_log_level_str_ = component_log_levels; std::vector log_levels = absl::StrSplit(component_log_levels, ','); for (auto& level : log_levels) { std::vector log_name_level = absl::StrSplit(level, ':'); if (log_name_level.size() != 2) { logError(fmt::format("error: component log level not correctly specified '{}'", level)); } std::string log_name = log_name_level[0]; spdlog::level::level_enum log_level = parseAndValidateLogLevel(log_name_level[1]); Logger::Logger* logger_to_change = Logger::Registry::logger(log_name); if (!logger_to_change) { logError(fmt::format("error: invalid component specified '{}'", log_name)); } component_log_levels_.push_back(std::make_pair(log_name, log_level)); } } uint32_t OptionsImpl::count() const { return count_; } void OptionsImpl::logError(const std::string& error) const { throw MalformedArgvException(error); } Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const { Server::CommandLineOptionsPtr command_line_options = std::make_unique(); command_line_options->set_base_id(baseId()); command_line_options->set_use_dynamic_base_id(useDynamicBaseId()); command_line_options->set_base_id_path(baseIdPath()); command_line_options->set_concurrency(concurrency()); command_line_options->set_config_path(configPath()); command_line_options->set_config_yaml(configYaml()); command_line_options->set_allow_unknown_static_fields(allow_unknown_static_fields_); command_line_options->set_reject_unknown_dynamic_fields(reject_unknown_dynamic_fields_); command_line_options->set_ignore_unknown_dynamic_fields(ignore_unknown_dynamic_fields_); command_line_options->set_admin_address_path(adminAddressPath()); command_line_options->set_component_log_level(component_log_level_str_); command_line_options->set_log_level(spdlog::level::to_string_view(logLevel()).data(), spdlog::level::to_string_view(logLevel()).size()); command_line_options->set_log_format(logFormat()); command_line_options->set_log_format_escaped(logFormatEscaped()); command_line_options->set_enable_fine_grain_logging(enableFineGrainLogging()); command_line_options->set_log_path(logPath()); command_line_options->set_service_cluster(serviceClusterName()); command_line_options->set_service_node(serviceNodeName()); command_line_options->set_service_zone(serviceZone()); if (mode() == Server::Mode::Serve) { command_line_options->set_mode(envoy::admin::v3::CommandLineOptions::Serve); } else if (mode() == Server::Mode::Validate) { command_line_options->set_mode(envoy::admin::v3::CommandLineOptions::Validate); } else { command_line_options->set_mode(envoy::admin::v3::CommandLineOptions::InitOnly); } if (localAddressIpVersion() == Network::Address::IpVersion::v4) { command_line_options->set_local_address_ip_version(envoy::admin::v3::CommandLineOptions::v4); } else { command_line_options->set_local_address_ip_version(envoy::admin::v3::CommandLineOptions::v6); } command_line_options->mutable_file_flush_interval()->MergeFrom( Protobuf::util::TimeUtil::MillisecondsToDuration(fileFlushIntervalMsec().count())); command_line_options->mutable_drain_time()->MergeFrom( Protobuf::util::TimeUtil::SecondsToDuration(drainTime().count())); command_line_options->set_drain_strategy(drainStrategy() == Server::DrainStrategy::Immediate ? envoy::admin::v3::CommandLineOptions::Immediate : envoy::admin::v3::CommandLineOptions::Gradual); command_line_options->mutable_parent_shutdown_time()->MergeFrom( Protobuf::util::TimeUtil::SecondsToDuration(parentShutdownTime().count())); command_line_options->set_disable_hot_restart(hotRestartDisabled()); command_line_options->set_enable_mutex_tracing(mutexTracingEnabled()); command_line_options->set_cpuset_threads(cpusetThreadsEnabled()); command_line_options->set_restart_epoch(restartEpoch()); for (const auto& e : disabledExtensions()) { command_line_options->add_disabled_extensions(e); } command_line_options->set_socket_path(socketPath()); command_line_options->set_socket_mode(socketMode()); return command_line_options; } OptionsImpl::OptionsImpl(const std::string& service_cluster, const std::string& service_node, const std::string& service_zone, spdlog::level::level_enum log_level) : base_id_(0u), use_dynamic_base_id_(false), base_id_path_(""), concurrency_(1u), config_path_(""), config_yaml_(""), local_address_ip_version_(Network::Address::IpVersion::v4), log_level_(log_level), log_format_(Logger::Logger::DEFAULT_LOG_FORMAT), log_format_escaped_(false), restart_epoch_(0u), service_cluster_(service_cluster), service_node_(service_node), service_zone_(service_zone), file_flush_interval_msec_(10000), drain_time_(600), parent_shutdown_time_(900), drain_strategy_(Server::DrainStrategy::Gradual), mode_(Server::Mode::Serve), hot_restart_disabled_(false), signal_handling_enabled_(true), mutex_tracing_enabled_(false), cpuset_threads_(false), socket_path_("@envoy_domain_socket"), socket_mode_(0) {} void OptionsImpl::disableExtensions(const std::vector& names) { for (const auto& name : names) { const std::vector parts = absl::StrSplit(name, absl::MaxSplits('/', 1)); if (parts.size() != 2) { ENVOY_LOG_MISC(warn, "failed to disable invalid extension name '{}'", name); continue; } if (Registry::FactoryCategoryRegistry::disableFactory(parts[0], parts[1])) { ENVOY_LOG_MISC(info, "disabled extension '{}'", name); } else { ENVOY_LOG_MISC(warn, "failed to disable unknown extension '{}'", name); } } } } // namespace Envoy