Skip to content

Commit

Permalink
Adding in PoolFactory class with MongoURI munging (mongodb#61)
Browse files Browse the repository at this point in the history
* Added in a Uri class that pops a true uri in and out of mongoc
* Added in the server clang-format
* Made generate-uuid.sh script
  • Loading branch information
bcaimano authored Dec 17, 2018
1 parent 195ae08 commit 3ce75fa
Show file tree
Hide file tree
Showing 9 changed files with 426 additions and 19 deletions.
File renamed without changes.
34 changes: 17 additions & 17 deletions create-new-actor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ usage() {
}

create_header_text() {
local uuid
local uuid_tag
local actor_name
uuid="$1"
uuid_tag="$1"
actor_name="$2"

echo "#ifndef HEADER_$uuid"
echo "#define HEADER_$uuid"
echo "#ifndef $uuid_tag"
echo "#define $uuid_tag"
echo ""
echo "#include <gennylib/Actor.hpp>"
echo "#include <gennylib/PhaseLoop.hpp>"
Expand Down Expand Up @@ -46,13 +46,13 @@ create_header_text() {
echo ""
echo "} // namespace genny::actor"
echo ""
echo "#endif // HEADER_$uuid"
echo "#endif // $uuid_tag"
}

create_impl_text() {
local uuid
local uuid_tag
local actor_name
uuid="$1"
uuid_tag="$1"
actor_name="$2"

echo "#include <memory>"
Expand Down Expand Up @@ -84,28 +84,28 @@ create_impl_text() {
}

create_header() {
local uuid
local uuid_tag
local actor_name
uuid="$1"
uuid_tag="$1"
actor_name="$2"

create_header_text "$@" > "$(dirname "$0")/src/cast_core/include/cast_core/actors/${actor_name}.hpp"
}

create_impl() {
local uuid
local uuid_tag
local actor_name
uuid="$1"
uuid_tag="$1"
actor_name="$2"

create_impl_text "$@" > "$(dirname "$0")/src/cast_core/src/actors/${actor_name}.cpp"
}

recreate_cast_core_cmake_file() {
local uuid
local uuid_tag
local actor_name
local cmake_file
uuid="$1"
uuid_tag="$1"
actor_name="$2"
cmake_file="$(dirname "$0")/src/cast_core/CMakeLists.txt"

Expand All @@ -131,8 +131,8 @@ if [ -z "$actor_name" ]; then
exit 2
fi

uuid="$(uuidgen | sed s/-/_/g)"
uuid_tag="$("$(dirname "$0")/generate-uuid-tag.sh")"

create_header "$uuid" "$actor_name"
create_impl "$uuid" "$actor_name"
recreate_cast_core_cmake_file "$uuid" "$actor_name"
create_header "$uuid_tag" "$actor_name"
create_impl "$uuid_tag" "$actor_name"
recreate_cast_core_cmake_file "$uuid_tag" "$actor_name"
4 changes: 4 additions & 0 deletions generate-uuid-tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#/bin/bash

UUID=$(uuidgen | sed s/-/_/g | tr a-z A-Z)
echo "HEADER_${UUID}"
2 changes: 2 additions & 0 deletions src/gennylib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_library(gennylib SHARED
src/ActorProducer.cpp
src/Cast.cpp
src/Orchestrator.cpp
src/PoolFactory.cpp

src/value_generators/parser.cpp
src/value_generators/value_generators.cpp
Expand Down Expand Up @@ -71,6 +72,7 @@ add_executable(test_gennylib
test/Cast_test.cpp
test/context_test.cpp
test/conventions_test.cpp
test/PoolFactory_test.cpp
test/PhaseLoop_test.cpp
test/version_test.cpp
test/metrics_test.cpp
Expand Down
53 changes: 53 additions & 0 deletions src/gennylib/include/gennylib/PoolFactory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifndef HEADER_3BB17688_900D_4AFB_B736_C9EC8DA9E33B
#define HEADER_3BB17688_900D_4AFB_B736_C9EC8DA9E33B

#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <string_view>

#include <mongocxx/pool.hpp>

namespace genny {

/**
* A pool factory takes in a MongoURI, modifies its components, and makes a pool from it
*
* This class allows for programically modifying all non-host components of the MongoURI.
* Any query parameter can be set via `setStringOption()`, `setIntOption()`, or `setFlag()`.
* It also allows for setting the protocol, username, password, and database via the options
* "Protocol", "Username", "Password", and "Database" in the same manner as query parameters would
* be set. Lastly, it allows for programatically setting up the ssl options for the connection pool
* via `configureSsl()`.
*/
class PoolFactory {
public:
PoolFactory(std::string_view uri);
~PoolFactory();

std::string makeUri() const;
std::unique_ptr<mongocxx::pool> makePool() const;

/**
* Options of note:
* minPoolSize
* maxPoolSize
* connectTimeoutMS
* socketTimeoutMS
*/
void setStringOption(const std::string & option, std::string value);
void setIntOption(const std::string & option, int32_t value);
void setFlag(const std::string & option, bool value = true);

void configureSsl(mongocxx::options::ssl options, bool enableSsl = true);

private:
struct Config;
std::unique_ptr<Config> _config;
};

} // namespace genny

#endif // HEADER_3BB17688_900D_4AFB_B736_C9EC8DA9E33B
150 changes: 150 additions & 0 deletions src/gennylib/src/PoolFactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#include <gennylib/PoolFactory.hpp>

#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <sstream>

#include <boost/log/trivial.hpp>
#include <mongocxx/uri.hpp>

#include <gennylib/InvalidConfigurationException.hpp>

namespace genny {

struct PoolFactory::Config {
Config(std::string_view uri) {
const auto protocolRegex = std::regex("^(mongodb://|mongodb+srv://)?(([^:@]*):([^@]*)@)?");
const auto hostRegex = std::regex("^,?([^:,/]+(:[0-9]+)?)");
const auto dbRegex = std::regex("^/([^?]*)\\??");
const auto queryRegex = std::regex("^&?([^=&]*)=([^&]*)");

std::cmatch matches;
auto i = 0;

// Extract the protocol, and optionally the username and the password
std::regex_search(uri.substr(i).data(), matches, protocolRegex);
if (matches.length(1)) {
accessOptions["Protocol"] = matches[1];
} else {
accessOptions["Protocol"] = "mongodb://";
}
if (matches.length(3))
accessOptions["Username"] = matches[3];
if (matches.length(4))
accessOptions["Password"] = matches[4];
i += matches.length();

// Extract each host specified in the uri
while (std::regex_search(uri.substr(i).data(), matches, hostRegex)) {
hosts.insert(matches[1]);
i += matches.length();
}

// Extract the db name and optionally the query string prefix
std::regex_search(uri.substr(i).data(), matches, dbRegex);
accessOptions["Database"] = matches[1];
i += matches.length();

// Extract each query parameter
// Note that the official syntax of query strings is poorly defined, keys without values may
// be valid but not supported here.
while (std::regex_search(uri.substr(i).data(), matches, queryRegex)) {
auto key = matches[1];
auto value = matches[2];
queryOptions[key] = value;
i += matches.length();
}
}

auto makeUri() const {
std::ostringstream ss;
size_t i;

ss << accessOptions.at("Protocol");
if (!accessOptions.at("Username").empty()) {
ss << accessOptions.at("Username") << ':' << accessOptions.at("Password") << '@';
}

i = 0;
for (auto& host : hosts) {
if (i++ > 0) {
ss << ',';
}
ss << host;
}

auto dbName = accessOptions.at("Database");
if (!dbName.empty() || !queryOptions.empty()) {
ss << '/' << accessOptions.at("Database");
}

if (!queryOptions.empty()) {
ss << '?';
}

i = 0;
for (auto && [ key, value ] : queryOptions) {
if (i++ > 0) {
ss << '&';
}
ss << key << '=' << value;
}

return ss.str();
}

std::set<std::string> hosts;
std::map<std::string, std::string> queryOptions;
std::map<std::string, std::string> accessOptions = {
{"Protocol", ""}, {"Username", ""}, {"Password", ""}, {"Database", ""},
};
mongocxx::options::pool poolOptions;
};

PoolFactory::PoolFactory(std::string_view rawUri) : _config(std::make_unique<Config>(rawUri)) {}
PoolFactory::~PoolFactory() {}

std::string PoolFactory::makeUri() const {
return _config->makeUri();
}

std::unique_ptr<mongocxx::pool> PoolFactory::makePool() const {
auto uriStr = makeUri();
BOOST_LOG_TRIVIAL(info) << "Constructing pool with MongoURI '" << uriStr << "'";

auto uri = mongocxx::uri{uriStr};
return std::make_unique<mongocxx::pool>(uri, _config->poolOptions);
}

void PoolFactory::setStringOption(const std::string& option, std::string value) {
// If the value is in the accessOptions set, set it
auto it = _config->accessOptions.find(option);
if (it != _config->accessOptions.end()) {
it->second = value;
return;
}

// Treat the value as a normal query parameter
_config->queryOptions[option] = value;
}

void PoolFactory::setIntOption(const std::string& option, int32_t value) {
auto valueStr = std::to_string(value);
setStringOption(option, valueStr);
}

void PoolFactory::setFlag(const std::string& option, bool value) {
auto valueStr = value ? "true" : "false";
setStringOption(option, valueStr);
}

void PoolFactory::configureSsl(mongocxx::options::ssl options, bool enableSsl) {
setFlag("ssl", enableSsl);

auto clientOpts = _config->poolOptions.client_opts();
_config->poolOptions = clientOpts.ssl_opts(options);
}

} // namespace genny
4 changes: 3 additions & 1 deletion src/gennylib/src/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <mongocxx/uri.hpp>

#include <gennylib/Cast.hpp>
#include <gennylib/PoolFactory.hpp>

namespace genny {

Expand All @@ -27,7 +28,8 @@ WorkloadContext::WorkloadContext(YAML::Node node,
mongocxx::instance::current();

// TODO: make this optional and default to mongodb://localhost:27017
_clientPool = std::make_unique<mongocxx::pool>(mongocxx::uri{mongoUri});
auto poolFactory = PoolFactory(mongoUri);
_clientPool = poolFactory.makePool();

// Make a bunch of actor contexts
for (const auto& actor : get_static(node, "Actors")) {
Expand Down
Loading

0 comments on commit 3ce75fa

Please sign in to comment.