Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Generate config descriptions #1842

Merged
11 changes: 11 additions & 0 deletions src/app/CliArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "migration/MigrationApplication.hpp"
#include "util/build/Build.hpp"
#include "util/newconfig/ConfigDescription.hpp"

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
Expand All @@ -44,6 +45,7 @@
description.add_options()
("help,h", "print help message and exit")
("version,v", "print version and exit")
("description,d", "generate config description")
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
("conf,c", po::value<std::string>()->default_value(kDEFAULT_CONFIG_PATH), "configuration file")
("ng-web-server,w", "Use ng-web-server")
("migrate", po::value<std::string>(), "start migration helper")
Expand All @@ -67,6 +69,15 @@
return Action{Action::Exit{EXIT_SUCCESS}};
}

if (parsed.count("description") != 0u) {
auto const res = util::config::ClioConfigDescription::getMarkdown();
if (res.has_value())
return Action{Action::Exit{EXIT_SUCCESS}};

std::cerr << res.error().error << std::endl;
return Action{Action::Exit{EXIT_FAILURE}};

Check warning on line 78 in src/app/CliArgs.cpp

View check run for this annotation

Codecov / codecov/patch

src/app/CliArgs.cpp#L78

Added line #L78 was not covered by tests
}

auto configPath = parsed["conf"].as<std::string>();

if (parsed.count("migrate") != 0u) {
Expand Down
1 change: 1 addition & 0 deletions src/util/newconfig/ConfigDefinition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ ClioConfigDefinition::getObject(std::string_view prefix, std::optional<std::size
auto const hasPrefix = mapKey.starts_with(prefixWithDot);
if (idx.has_value() && hasPrefix && std::holds_alternative<Array>(mapVal)) {
ASSERT(std::get<Array>(mapVal).size() > idx.value(), "Index provided is out of scope");

// we want to support getObject("array") and getObject("array.[]"), so we check if "[]" exists
if (!prefix.contains("[]"))
return ObjectView{prefixWithDot + "[]", idx.value(), *this};
Expand Down
20 changes: 0 additions & 20 deletions src/util/newconfig/ConfigDefinition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,26 +84,6 @@ class ClioConfigDefinition {
[[nodiscard]] std::optional<std::vector<Error>>
parse(ConfigFileInterface const& config);

/**
* @brief Validates the configuration file
*
* Should only check for valid values, without populating
*
* @param config The configuration file interface
* @return An optional vector of Error objects stating all the failures if validation fails
*/
[[nodiscard]] std::optional<std::vector<Error>>
validate(ConfigFileInterface const& config) const;

/**
* @brief Generate markdown file of all the clio config descriptions
*
* @param configDescription The configuration description object
* @return An optional Error if generating markdown fails
*/
[[nodiscard]] std::expected<std::string, Error>
getMarkdown(ClioConfigDescription const& configDescription) const;

/**
* @brief Returns the ObjectView specified with the prefix
*
Expand Down
109 changes: 84 additions & 25 deletions src/util/newconfig/ConfigDescription.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@
#pragma once

#include "util/Assert.hpp"
#include "util/newconfig/Error.hpp"

#include <fmt/core.h>

#include <algorithm>
#include <array>
#include <expected>
#include <fstream>
#include <iostream>
#include <string_view>

namespace util::config {
Expand All @@ -34,6 +40,9 @@
*/
struct ClioConfigDescription {
public:
/** @brief Name of the Markdown file containing detailed descriptions of all configuration values. */
static constexpr auto kCONFIG_DESCRIPTION_FILE_NAME = "Config-Descriptions.md";
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved

/** @brief Struct to represent a key-value pair*/
struct KV {
std::string_view key;
Expand Down Expand Up @@ -61,26 +70,62 @@
return itr->value;
}

/**
* @brief Generate markdown file of all the clio config descriptions
*
* @return An Error if generating markdown fails, otherwise nothing
*/
[[nodiscard]] static std::expected<void, Error>
getMarkdown()
{
std::ofstream file(kCONFIG_DESCRIPTION_FILE_NAME);

if (!file.is_open())
return std::unexpected<Error>{fmt::format("failed to create file: {}", kCONFIG_DESCRIPTION_FILE_NAME)};

Check warning on line 84 in src/util/newconfig/ConfigDescription.hpp

View check run for this annotation

Codecov / codecov/patch

src/util/newconfig/ConfigDescription.hpp#L84

Added line #L84 was not covered by tests
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved

file << "# Config Description Markdown File\n";
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
file << "This is a **markdown** file listing all Clio Configurations in detail.\n\n";
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
file << "## Configuration Details\n\n";

for (auto const& [key, val] : kCONFIG_DESCRIPTION) {
file << "### " << key << "\n";
file << val << "\n\n";
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
}
file << "```\n";

// Close the file
file.close();

std::cout << "Markdown file generated successfully: Config-Descriptions.md" << "\n";
return {};
}

private:
static constexpr auto kCONFIG_DESCRIPTION = std::array{
KV{.key = "database.type", .value = "Type of database to use. Default is Scylladb."},
KV{.key = "database.type",
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
.value = "Type of database to use. We currently support Cassandra and Scylladb. We default to Scylladb."},
KV{.key = "database.cassandra.contact_points",
.value =
"A list of IP addresses or hostnames of the initial nodes (Cassandra/Scylladb cluster nodes) that the "
"client will connect to when establishing a connection with the database."},
.value = "A list of IP addresses or hostnames of the initial nodes (Cassandra/Scylladb cluster nodes) that "
"the client will connect to when establishing a connection with the database. If you're running "
"locally, it should be 'localhost' or 127.0.0.1"},
KV{.key = "database.cassandra.secure_connect_bundle",
.value = "Configuration file that contains the necessary security credentials and connection details for "
"securely "
"connecting to a Cassandra database cluster."},
KV{.key = "database.cassandra.port", .value = "Port number to connect to Cassandra."},
KV{.key = "database.cassandra.keyspace", .value = "Keyspace to use in Cassandra."},
KV{.key = "database.cassandra.replication_factor", .value = "Number of replicated nodes for Scylladb."},
KV{.key = "database.cassandra.table_prefix", .value = "Prefix for Cassandra table names."},
KV{.key = "database.cassandra.port", .value = "Port number to connect to the database."},
KV{.key = "database.cassandra.keyspace", .value = "Keyspace to use for the database."},
KV{.key = "database.cassandra.replication_factor",
.value = "Number of replicated nodes for Scylladb. Visit “here for more details : "
"https://university.scylladb.com/courses/scylla-essentials-overview/lessons/high-availability/"
"topic/fault-tolerance-replication-factor/ "},
KV{.key = "database.cassandra.table_prefix", .value = "Prefix for Database table names."},
KV{.key = "database.cassandra.max_write_requests_outstanding",
.value = "Maximum number of outstanding write requests."},
.value = "Maximum number of outstanding write requests. Write requests are api calls that write to database "
},
KV{.key = "database.cassandra.max_read_requests_outstanding",
.value = "Maximum number of outstanding read requests."},
KV{.key = "database.cassandra.threads", .value = "Number of threads for Cassandra operations."},
.value = "Maximum number of outstanding read requests, which reads from database"},
KV{.key = "database.cassandra.threads", .value = "Number of threads that will be used for database operations."
},
KV{.key = "database.cassandra.core_connections_per_host",
.value = "Number of core connections per host for Cassandra."},
KV{.key = "database.cassandra.queue_size_io", .value = "Queue size for I/O operations in Cassandra."},
Expand All @@ -106,9 +151,9 @@
.value = "Timeout duration for the forwarding cache used in Rippled communication."},
KV{.key = "forwarding.request_timeout",
.value = "Timeout duration for the forwarding request used in Rippled communication."},
KV{.key = "rpc.cache_timeout", .value = "Timeout duration for the rpc request."},
KV{.key = "rpc.cache_timeout", .value = "Timeout duration for RPC requests."},
KV{.key = "num_markers",
.value = "The number of markers is the number of coroutines to load the cache concurrently."},
.value = "The number of markers is the number of coroutines to download the initial ledger"},
KV{.key = "dos_guard.[].whitelist", .value = "List of IP addresses to whitelist for DOS protection."},
KV{.key = "dos_guard.max_fetches", .value = "Maximum number of fetch operations allowed by DOS guard."},
KV{.key = "dos_guard.max_connections", .value = "Maximum number of concurrent connections allowed by DOS guard."
Expand All @@ -120,8 +165,11 @@
KV{.key = "server.port", .value = "Port number of the Clio HTTP server."},
KV{.key = "server.max_queue_size",
.value = "Maximum size of the server's request queue. Value of 0 is no limit."},
KV{.key = "server.local_admin", .value = "Indicates if the server should run with admin privileges."},
KV{.key = "server.admin_password", .value = "Password for Clio admin-only APIs."},
KV{.key = "server.local_admin",
.value = "Indicates if the server should run with admin privileges. Only one of local_admin or "
"admin_password can be set."},
KV{.key = "server.admin_password",
.value = "Password for Clio admin-only APIs. Only one of local_admin or admin_password can be set."},
KV{.key = "server.processing_policy",
.value = R"(Could be "sequent" or "parallel". For the sequent policy, requests from a single client
connection are processed one by one, with the next request read only after the previous one is processed. For the parallel policy, Clio will accept
Expand All @@ -133,26 +181,37 @@
KV{.key = "server.ws_max_sending_queue_size", .value = "Maximum size of the websocket sending queue."},
KV{.key = "prometheus.enabled", .value = "Enable or disable Prometheus metrics."},
KV{.key = "prometheus.compress_reply", .value = "Enable or disable compression of Prometheus responses."},
KV{.key = "io_threads", .value = "Number of I/O threads. Value must be greater than 1"},
KV{.key = "io_threads", .value = "Number of I/O threads. Value cannot be less than 1"},
KV{.key = "subscription_workers",
.value = "The number of worker threads or processes that are responsible for managing and processing "
"subscription-based tasks."},
"subscription-based tasks from rippled"},
KV{.key = "graceful_period", .value = "Number of milliseconds server will wait to shutdown gracefully."},
KV{.key = "cache.num_diffs", .value = "Number of diffs to cache."},
KV{.key = "cache.num_diffs", .value = "Number of diffs to cache. For more info, consult readme.md in etc"},
KV{.key = "cache.num_markers", .value = "Number of markers to cache."},
KV{.key = "cache.num_cursors_from_diff", .value = "Num of cursors that are different."},
KV{.key = "cache.num_cursors_from_account", .value = "Number of cursors from an account."},
KV{.key = "cache.page_fetch_size", .value = "Page fetch size for cache operations."},
KV{.key = "cache.load", .value = "Cache loading strategy ('sync' or 'async')."},
KV{.key = "log_channels.[].channel", .value = "Name of the log channel."},
KV{.key = "log_channels.[].log_level", .value = "Log level for the log channel."},
KV{.key = "log_level", .value = "General logging level of Clio."},
KV{.key = "log_channels.[].channel",
.value = "Name of the log channel. Possible values only include 'Backend', 'Webserver', 'Subscriptions', "
"'RPC', 'ETL', and 'Performance'"},
KV{.key = "log_channels.[].log_level",
.value = "Log level for the specific log channel. Possible values only include `trace`, `debug`, `info`, "
"`warning`, `error`, `fatal`"},
KV{.key = "log_level",
.value = "General logging level of Clio. Possible values only include `trace`, `debug`, `info`, `warning`, "
"`error`, `fatal`"},
KV{.key = "log_format", .value = "Format string for log messages."},
KV{.key = "log_to_console", .value = "Enable or disable logging to console."},
KV{.key = "log_directory", .value = "Directory path for log files."},
KV{.key = "log_rotation_size", .value = "Log rotation size in megabytes."},
KV{.key = "log_rotation_size",
.value =
"Log rotation size in megabytes. When the log file reaches this particular size, a new log file starts."
},
KV{.key = "log_directory_max_size", .value = "Maximum size of the log directory in megabytes."},
KV{.key = "log_rotation_hour_interval", .value = "Interval in hours for log rotation."},
KV{.key = "log_rotation_hour_interval",
.value = "Interval in hours for log rotation. If the current log file reaches this value in logging, a new "
"log file starts."},
KV{.key = "log_tag_style", .value = "Style for log tags."},
KV{.key = "extractor_threads", .value = "Number of extractor threads."},
KV{.key = "read_only", .value = "Indicates if the server should have read-only privileges."},
Expand All @@ -164,8 +223,8 @@
KV{.key = "api_version.default", .value = "Default API version Clio will run on."},
KV{.key = "api_version.min", .value = "Minimum API version."},
KV{.key = "api_version.max", .value = "Maximum API version."},
KV{.key = "migration.full_scan_threads", .value = "The number of threads used to scan table."},
KV{.key = "migration.full_scan_jobs", .value = "The number of coroutines used to scan table."},
KV{.key = "migration.full_scan_threads", .value = "The number of threads used to scan the table."},
KV{.key = "migration.full_scan_jobs", .value = "The number of coroutines used to scan the table."},
KV{.key = "migration.cursors_per_job", .value = "The number of cursors each coroutine will scan."}
};
};
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/app/CliArgsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
//==============================================================================

#include "app/CliArgs.hpp"
#include "util/newconfig/ConfigDescription.hpp"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <array>
#include <cstdlib>
#include <filesystem>
#include <string_view>

using namespace app;
Expand Down Expand Up @@ -145,3 +147,24 @@ TEST_F(CliArgsTests, Parse_VerifyConfig)
returnCode
);
}

TEST_F(CliArgsTests, Parse_ConfigDescription)
{
using namespace util::config;
std::array argv{"clio_server", "--description"}; // NOLINT(bugprone-suspicious-stringview-data-usage)
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
auto const action = CliArgs::parse(argv.size(), argv.data());
EXPECT_CALL(onExitMock, Call).WillOnce([](CliArgs::Action::Exit const& exit) { return exit.exitCode; });

ASSERT_TRUE(std::filesystem::exists(ClioConfigDescription::kCONFIG_DESCRIPTION_FILE_NAME));
std::filesystem::remove(ClioConfigDescription::kCONFIG_DESCRIPTION_FILE_NAME);

EXPECT_EQ(
action.apply(
onRunMock.AsStdFunction(),
onExitMock.AsStdFunction(),
onMigrateMock.AsStdFunction(),
onVerifyMock.AsStdFunction()
),
EXIT_SUCCESS
);
}
5 changes: 4 additions & 1 deletion tests/unit/util/newconfig/ClioConfigDefinitionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ TEST(ConfigDescription, GetValues)
{
ClioConfigDescription const definition{};

EXPECT_EQ(definition.get("database.type"), "Type of database to use. Default is Scylladb.");
EXPECT_EQ(
definition.get("database.type"),
"Type of database to use. We currently support Cassandra and Scylladb. We default to Scylladb."
PeterChen13579 marked this conversation as resolved.
Show resolved Hide resolved
);
EXPECT_EQ(definition.get("etl_sources.[].ip"), "IP address of the ETL source.");
EXPECT_EQ(definition.get("prometheus.enabled"), "Enable or disable Prometheus metrics.");
}
Expand Down
Loading