enh(MongoDB): Add parsing of mongodb replica set URI.

This commit is contained in:
Matej Kenda
2025-11-27 09:00:34 +01:00
parent d0c2a6c266
commit 2c41d08b4a
6 changed files with 303 additions and 40 deletions

View File

@@ -116,8 +116,18 @@ public:
explicit ReplicaSet(const std::string& uri);
/// Creates a ReplicaSet from a MongoDB URI.
/// Format: mongodb://host1:port1,host2:port2,...?options
///
/// Supported URI options:
/// - replicaSet=name - Replica set name
/// - readPreference=mode - primary|primaryPreferred|secondary|secondaryPreferred|nearest
/// - connectTimeoutMS=ms - Connection timeout in milliseconds
/// - socketTimeoutMS=ms - Socket timeout in milliseconds
/// - heartbeatFrequencyMS=ms - Heartbeat frequency in milliseconds
///
/// Example: mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0&readPreference=primaryPreferred
///
/// Throws Poco::SyntaxException if URI is invalid.
/// NOTE: This is a placeholder for future URI support.
/// Throws Poco::UnknownURISchemeException if scheme is not "mongodb".
virtual ~ReplicaSet();
/// Destroys the ReplicaSet and stops background monitoring.
@@ -186,7 +196,8 @@ private:
/// Queries all known servers and updates topology.
void parseURI(const std::string& uri);
/// Parses a MongoDB URI into configuration (future implementation).
/// Parses a MongoDB URI into configuration.
/// Extracts hosts and query parameters into _config.
Config _config;
TopologyDescription _topology;

View File

@@ -9,3 +9,9 @@ set(MONITOR_NAME "ReplicaSetMonitor")
set(MONITOR_SRCS src/ReplicaSetMonitor.cpp)
add_executable(${MONITOR_NAME} ${MONITOR_SRCS})
target_link_libraries(${MONITOR_NAME} Poco::MongoDB Poco::Net Poco::Foundation)
# URIExample sample - demonstrates URI parsing
set(URI_NAME "URIExample")
set(URI_SRCS src/URIExample.cpp)
add_executable(${URI_NAME} ${URI_SRCS})
target_link_libraries(${URI_NAME} Poco::MongoDB Poco::Net Poco::Foundation)

View File

@@ -20,7 +20,10 @@ A continuous monitoring tool that performs regular read/write operations and dis
# Basic usage with defaults
./ReplicaSetMonitor
# Specify replica set and hosts
# Using MongoDB URI (recommended)
./ReplicaSetMonitor -u 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0'
# Specify replica set and hosts (traditional method)
./ReplicaSetMonitor -s rs0 -H mongo1:27017,mongo2:27017,mongo3:27017
# Run with 10-second intervals for 100 iterations
@@ -29,7 +32,15 @@ A continuous monitoring tool that performs regular read/write operations and dis
# Verbose mode for detailed operation output
./ReplicaSetMonitor -v
# Full example
# Full example with URI
./ReplicaSetMonitor \
--uri 'mongodb://mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0&readPreference=primaryPreferred' \
--interval 5 \
--database test \
--collection health_check \
--verbose
# Full example with traditional options
./ReplicaSetMonitor \
--set rs0 \
--hosts mongo1:27017,mongo2:27017,mongo3:27017 \
@@ -44,6 +55,7 @@ A continuous monitoring tool that performs regular read/write operations and dis
| Option | Description | Default |
|--------|-------------|---------|
| `-h, --help` | Show help message | - |
| `-u, --uri URI` | MongoDB connection URI (takes precedence) | - |
| `-s, --set NAME` | Replica set name | `rs0` |
| `-H, --hosts HOSTS` | Comma-separated host:port list | `localhost:27017,localhost:27018,localhost:27019` |
| `-i, --interval SECONDS` | Check interval in seconds | `5` |
@@ -52,8 +64,11 @@ A continuous monitoring tool that performs regular read/write operations and dis
| `-v, --verbose` | Verbose output | `false` |
| `-n, --iterations N` | Number of iterations | unlimited |
**Note:** The `--uri` option takes precedence over `--set` and `--hosts` options.
### Environment Variables
- `MONGODB_URI`: MongoDB connection URI (takes precedence)
- `MONGODB_REPLICA_SET`: Replica set name
- `MONGODB_HOSTS`: Comma-separated host:port list
@@ -228,6 +243,84 @@ Demonstrates various replica set features with multiple commands.
---
## URIExample - URI Parsing Demonstration
Demonstrates MongoDB URI parsing and connection to replica sets.
### Features
- Parse MongoDB connection URIs
- Display parsed configuration (hosts, replica set name, read preference, timeouts)
- Connect to replica set and show topology
- Query server information
- Validate URI format
### Usage
```bash
./URIExample <uri>
```
### Examples
```bash
# Basic replica set URI
./URIExample 'mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0'
# With read preference
./URIExample 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0&readPreference=primaryPreferred'
# With custom timeouts and heartbeat
./URIExample 'mongodb://host1:27017,host2:27017/?replicaSet=rs0&connectTimeoutMS=5000&socketTimeoutMS=30000&heartbeatFrequencyMS=5000'
```
### Supported URI Options
- `replicaSet=name` - Replica set name
- `readPreference=mode` - Read preference (primary|primaryPreferred|secondary|secondaryPreferred|nearest)
- `connectTimeoutMS=ms` - Connection timeout in milliseconds
- `socketTimeoutMS=ms` - Socket timeout in milliseconds
- `heartbeatFrequencyMS=ms` - Heartbeat frequency in milliseconds
### Example Output
```
Parsing MongoDB Replica Set URI
================================================================================
URI: mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0
✓ URI parsed successfully!
Configuration:
--------------------------------------------------------------------------------
Replica Set Name: rs0
Read Preference: primary
Seed Servers: localhost:27017, localhost:27018, localhost:27019
Monitoring: Active
Connecting to replica set...
✓ Connected to primary: localhost:27017
Server Information:
--------------------------------------------------------------------------------
MongoDB Version: 7.0.5
Git Version: 7809d71e84e314b497f282ea52598668b08b84dd
Replica Set Topology:
--------------------------------------------------------------------------------
Set Name: rs0
Has Primary: Yes
Servers: 3
localhost:27017 [PRIMARY] RTT: 2.34 ms
localhost:27018 [SECONDARY] RTT: 3.12 ms
localhost:27019 [SECONDARY] RTT: 2.89 ms
✓ Success!
```
---
## Building the Examples
### With CMake
@@ -238,10 +331,12 @@ mkdir build && cd build
cmake .. -DENABLE_MONGODB=ON -DENABLE_SAMPLES=ON
cmake --build . --target ReplicaSetMonitor
cmake --build . --target ReplicaSet
cmake --build . --target URIExample
# Executables are in bin/
./bin/ReplicaSetMonitor --help
./bin/ReplicaSet basic
./bin/URIExample 'mongodb://localhost:27017/?replicaSet=rs0'
```
### With Make

View File

@@ -86,7 +86,7 @@ void basicExample()
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
config.readPreference = ReadPreference::Primary;
config.readPreference = ReadPreference(ReadPreference::Primary);
config.enableMonitoring = true;
// Create replica set
@@ -164,7 +164,7 @@ void readPreferenceExample()
// Primary read preference
std::cout << "1. Primary read preference:" << std::endl;
Connection::Ptr primaryConn = rs.getConnection(ReadPreference::Primary);
Connection::Ptr primaryConn = rs.getConnection(ReadPreference(ReadPreference::Primary));
if (!primaryConn.isNull())
{
std::cout << " Connected to: " << primaryConn->address().toString() << std::endl;
@@ -173,7 +173,7 @@ void readPreferenceExample()
// Secondary read preference
std::cout << "2. Secondary read preference:" << std::endl;
Connection::Ptr secondaryConn = rs.getConnection(ReadPreference::Secondary);
Connection::Ptr secondaryConn = rs.getConnection(ReadPreference(ReadPreference::Secondary));
if (!secondaryConn.isNull())
{
std::cout << " Connected to: " << secondaryConn->address().toString() << std::endl;
@@ -186,7 +186,7 @@ void readPreferenceExample()
// PrimaryPreferred read preference
std::cout << "3. PrimaryPreferred read preference:" << std::endl;
Connection::Ptr prefConn = rs.getConnection(ReadPreference::PrimaryPreferred);
Connection::Ptr prefConn = rs.getConnection(ReadPreference(ReadPreference::PrimaryPreferred));
if (!prefConn.isNull())
{
std::cout << " Connected to: " << prefConn->address().toString() << std::endl;
@@ -195,7 +195,7 @@ void readPreferenceExample()
// Nearest read preference (lowest latency)
std::cout << "4. Nearest read preference (lowest latency):" << std::endl;
Connection::Ptr nearestConn = rs.getConnection(ReadPreference::Nearest);
Connection::Ptr nearestConn = rs.getConnection(ReadPreference(ReadPreference::Nearest));
if (!nearestConn.isNull())
{
std::cout << " Connected to: " << nearestConn->address().toString() << std::endl;
@@ -222,11 +222,11 @@ void failoverExample()
ReplicaSet::Config config;
config.setName = setName;
config.seeds = parseHosts(hostsStr);
config.readPreference = ReadPreference::PrimaryPreferred;
config.readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
ReplicaSet rs(config);
// Create a replica set connection with automatic failover
ReplicaSetConnection::Ptr rsConn = new ReplicaSetConnection(rs, ReadPreference::PrimaryPreferred);
ReplicaSetConnection::Ptr rsConn = new ReplicaSetConnection(rs, ReadPreference(ReadPreference::PrimaryPreferred));
std::cout << "Using ReplicaSetConnection for automatic failover" << std::endl;
std::cout << "Initial connection: " << rsConn->address().toString() << std::endl;
@@ -302,7 +302,7 @@ void poolExample()
std::cout << std::endl;
// Create connection pool
PoolableObjectFactory<ReplicaSetConnection, ReplicaSetConnection::Ptr> factory(*rs, ReadPreference::PrimaryPreferred);
PoolableObjectFactory<ReplicaSetConnection, ReplicaSetConnection::Ptr> factory(*rs, ReadPreference(ReadPreference::PrimaryPreferred));
ObjectPool<ReplicaSetConnection, ReplicaSetConnection::Ptr> pool(factory, 5, 10);
// Use pooled connections

View File

@@ -42,6 +42,7 @@ using namespace Poco;
struct MonitorConfig
{
std::string uri;
std::string setName;
std::vector<Net::SocketAddress> seeds;
int intervalSeconds;
@@ -51,6 +52,7 @@ struct MonitorConfig
int maxIterations;
MonitorConfig():
uri(),
setName("rs0"),
intervalSeconds(5),
database("test"),
@@ -88,6 +90,8 @@ void printUsage()
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " -h, --help Show this help message" << std::endl;
std::cout << " -u, --uri URI MongoDB connection URI" << std::endl;
std::cout << " (e.g., mongodb://host1:27017,host2:27017/?replicaSet=rs0)" << std::endl;
std::cout << " -s, --set NAME Replica set name (default: rs0)" << std::endl;
std::cout << " -H, --hosts HOSTS Comma-separated host:port list" << std::endl;
std::cout << " (default: localhost:27017,localhost:27018,localhost:27019)" << std::endl;
@@ -97,11 +101,18 @@ void printUsage()
std::cout << " -v, --verbose Verbose output" << std::endl;
std::cout << " -n, --iterations N Number of iterations (default: unlimited)" << std::endl;
std::cout << std::endl;
std::cout << "Note: --uri takes precedence over --set and --hosts options." << std::endl;
std::cout << std::endl;
std::cout << "Environment variables:" << std::endl;
std::cout << " MONGODB_URI MongoDB connection URI" << std::endl;
std::cout << " MONGODB_REPLICA_SET Replica set name" << std::endl;
std::cout << " MONGODB_HOSTS Comma-separated host:port list" << std::endl;
std::cout << std::endl;
std::cout << "Example:" << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " # Using URI" << std::endl;
std::cout << " ReplicaSetMonitor -u 'mongodb://mongo1:27017,mongo2:27017/?replicaSet=rs0'" << std::endl;
std::cout << std::endl;
std::cout << " # Using separate options" << std::endl;
std::cout << " ReplicaSetMonitor -s rs0 -H mongo1:27017,mongo2:27017,mongo3:27017 -i 10" << std::endl;
std::cout << std::endl;
}
@@ -314,37 +325,53 @@ void runMonitor(const MonitorConfig& config)
{
try
{
// Configure replica set
ReplicaSet::Config rsConfig;
rsConfig.setName = config.setName;
rsConfig.seeds = config.seeds;
rsConfig.readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
rsConfig.enableMonitoring = true;
rsConfig.heartbeatFrequency = Poco::Timespan(5, 0); // 5 seconds
// Create replica set - use URI if provided, otherwise use Config
SharedPtr<ReplicaSet> rs;
std::cout << "Connecting to replica set: " << config.setName << std::endl;
std::cout << "Seed servers: ";
for (size_t i = 0; i < config.seeds.size(); ++i)
if (!config.uri.empty())
{
if (i > 0) std::cout << ", ";
std::cout << config.seeds[i].toString();
}
std::cout << std::endl;
std::cout << "Check interval: " << config.intervalSeconds << " seconds" << std::endl;
std::cout << std::endl;
// Use URI constructor
std::cout << "Connecting to replica set via URI" << std::endl;
std::cout << "URI: " << config.uri << std::endl;
std::cout << "Check interval: " << config.intervalSeconds << " seconds" << std::endl;
std::cout << std::endl;
ReplicaSet rs(rsConfig);
rs = new ReplicaSet(config.uri);
}
else
{
// Use Config constructor
ReplicaSet::Config rsConfig;
rsConfig.setName = config.setName;
rsConfig.seeds = config.seeds;
rsConfig.readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
rsConfig.enableMonitoring = true;
rsConfig.heartbeatFrequency = Poco::Timespan(5, 0); // 5 seconds
std::cout << "Connecting to replica set: " << config.setName << std::endl;
std::cout << "Seed servers: ";
for (size_t i = 0; i < config.seeds.size(); ++i)
{
if (i > 0) std::cout << ", ";
std::cout << config.seeds[i].toString();
}
std::cout << std::endl;
std::cout << "Check interval: " << config.intervalSeconds << " seconds" << std::endl;
std::cout << std::endl;
rs = new ReplicaSet(rsConfig);
}
std::cout << "Replica set connected successfully!" << std::endl;
std::cout << "Background monitoring active." << std::endl;
std::cout << std::endl;
// Print initial topology
printTopology(rs.topology(), config.verbose);
printTopology(rs->topology(), config.verbose);
// Create replica set connections for reads and writes
ReplicaSetConnection::Ptr writeConn = new ReplicaSetConnection(rs, ReadPreference(ReadPreference::Primary));
ReplicaSetConnection::Ptr readConn = new ReplicaSetConnection(rs, ReadPreference(ReadPreference::PrimaryPreferred));
ReplicaSetConnection::Ptr writeConn = new ReplicaSetConnection(*rs, ReadPreference(ReadPreference::Primary));
ReplicaSetConnection::Ptr readConn = new ReplicaSetConnection(*rs, ReadPreference(ReadPreference::PrimaryPreferred));
// Monitoring loop
int iteration = 0;
@@ -410,7 +437,7 @@ void runMonitor(const MonitorConfig& config)
if (iteration % 10 == 0 || iteration == 1)
{
std::cout << std::endl;
printTopology(rs.topology(), config.verbose);
printTopology(rs->topology(), config.verbose);
}
else
{
@@ -447,6 +474,12 @@ int main(int argc, char** argv)
MonitorConfig config;
// Parse environment variables
const char* envUri = std::getenv("MONGODB_URI");
if (envUri)
{
config.uri = envUri;
}
const char* envSet = std::getenv("MONGODB_REPLICA_SET");
if (envSet)
{
@@ -469,6 +502,10 @@ int main(int argc, char** argv)
printUsage();
return 0;
}
else if ((arg == "-u" || arg == "--uri") && i + 1 < argc)
{
config.uri = argv[++i];
}
else if ((arg == "-s" || arg == "--set") && i + 1 < argc)
{
config.setName = argv[++i];
@@ -506,8 +543,8 @@ int main(int argc, char** argv)
}
}
// Use defaults if not configured
if (config.seeds.empty())
// Use defaults if not configured and no URI provided
if (config.uri.empty() && config.seeds.empty())
{
config.seeds = parseHosts("localhost:27017,localhost:27018,localhost:27019");
}

View File

@@ -17,6 +17,8 @@
#include "Poco/Exception.h"
#include "Poco/Random.h"
#include "Poco/Thread.h"
#include "Poco/URI.h"
#include "Poco/NumberParser.h"
#include <chrono>
@@ -483,10 +485,122 @@ void ReplicaSet::updateTopologyFromAllServers()
void ReplicaSet::parseURI(const std::string& uri)
{
// Placeholder for URI parsing implementation
// This would parse mongodb://host1:port1,host2:port2,...?options
// For now, throw NotImplementedException
throw Poco::NotImplementedException("URI parsing not yet implemented. Use Config constructor instead.");
// Parse MongoDB URI: mongodb://[user:pass@]host1:port1,host2:port2[,hostN:portN]/[database][?options]
Poco::URI theURI(uri);
if (theURI.getScheme() != "mongodb")
{
throw Poco::UnknownURISchemeException("Replica set URI must use 'mongodb' scheme");
}
// Parse authority to extract multiple hosts
// The authority in MongoDB URIs can be: host1:port1,host2:port2,host3:port3
// Poco::URI will give us the full authority string, we need to parse it manually
std::string authority = theURI.getAuthority();
// Remove userinfo if present (username:password@)
std::string::size_type atPos = authority.find('@');
std::string hostsStr;
if (atPos != std::string::npos)
{
hostsStr = authority.substr(atPos + 1);
}
else
{
hostsStr = authority;
}
// Parse comma-separated hosts
_config.seeds.clear();
std::string::size_type start = 0;
std::string::size_type end;
while ((end = hostsStr.find(',', start)) != std::string::npos)
{
std::string hostPort = hostsStr.substr(start, end - start);
if (!hostPort.empty())
{
try
{
_config.seeds.push_back(Net::SocketAddress(hostPort));
}
catch (...)
{
// Skip invalid host addresses
}
}
start = end + 1;
}
// Parse last host
std::string lastHost = hostsStr.substr(start);
if (!lastHost.empty())
{
try
{
_config.seeds.push_back(Net::SocketAddress(lastHost));
}
catch (...)
{
// Skip invalid host address
}
}
if (_config.seeds.empty())
{
throw Poco::SyntaxException("No valid hosts found in replica set URI");
}
// Parse query parameters
Poco::URI::QueryParameters params = theURI.getQueryParameters();
for (const auto& param : params)
{
if (param.first == "replicaSet")
{
_config.setName = param.second;
}
else if (param.first == "readPreference")
{
// Parse read preference mode
if (param.second == "primary")
{
_config.readPreference = ReadPreference(ReadPreference::Primary);
}
else if (param.second == "primaryPreferred")
{
_config.readPreference = ReadPreference(ReadPreference::PrimaryPreferred);
}
else if (param.second == "secondary")
{
_config.readPreference = ReadPreference(ReadPreference::Secondary);
}
else if (param.second == "secondaryPreferred")
{
_config.readPreference = ReadPreference(ReadPreference::SecondaryPreferred);
}
else if (param.second == "nearest")
{
_config.readPreference = ReadPreference(ReadPreference::Nearest);
}
}
else if (param.first == "connectTimeoutMS")
{
Poco::Int64 timeoutMs = Poco::NumberParser::parse64(param.second);
_config.connectTimeout = Poco::Timespan(timeoutMs * 1000); // Convert ms to microseconds
}
else if (param.first == "socketTimeoutMS")
{
Poco::Int64 timeoutMs = Poco::NumberParser::parse64(param.second);
_config.socketTimeout = Poco::Timespan(timeoutMs * 1000); // Convert ms to microseconds
}
else if (param.first == "heartbeatFrequencyMS")
{
Poco::Int64 freqMs = Poco::NumberParser::parse64(param.second);
_config.heartbeatFrequency = Poco::Timespan(freqMs * 1000); // Convert ms to microseconds
}
// Note: readPreferenceTags and maxStalenessSeconds would require more complex parsing
// and are not commonly used, so we skip them for now
}
}