diff --git a/MongoDB/include/Poco/MongoDB/ReplicaSet.h b/MongoDB/include/Poco/MongoDB/ReplicaSet.h index 1a62c0472..f5ff6785e 100644 --- a/MongoDB/include/Poco/MongoDB/ReplicaSet.h +++ b/MongoDB/include/Poco/MongoDB/ReplicaSet.h @@ -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; diff --git a/MongoDB/samples/ReplicaSet/CMakeLists.txt b/MongoDB/samples/ReplicaSet/CMakeLists.txt index 30b42b871..b0c1a2b3f 100644 --- a/MongoDB/samples/ReplicaSet/CMakeLists.txt +++ b/MongoDB/samples/ReplicaSet/CMakeLists.txt @@ -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) diff --git a/MongoDB/samples/ReplicaSet/README.md b/MongoDB/samples/ReplicaSet/README.md index ca0988eb5..19d882b40 100644 --- a/MongoDB/samples/ReplicaSet/README.md +++ b/MongoDB/samples/ReplicaSet/README.md @@ -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 +``` + +### 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 diff --git a/MongoDB/samples/ReplicaSet/src/ReplicaSet.cpp b/MongoDB/samples/ReplicaSet/src/ReplicaSet.cpp index 0eaf24740..c9dd0f1fa 100644 --- a/MongoDB/samples/ReplicaSet/src/ReplicaSet.cpp +++ b/MongoDB/samples/ReplicaSet/src/ReplicaSet.cpp @@ -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 factory(*rs, ReadPreference::PrimaryPreferred); + PoolableObjectFactory factory(*rs, ReadPreference(ReadPreference::PrimaryPreferred)); ObjectPool pool(factory, 5, 10); // Use pooled connections diff --git a/MongoDB/samples/ReplicaSet/src/ReplicaSetMonitor.cpp b/MongoDB/samples/ReplicaSet/src/ReplicaSetMonitor.cpp index 4a0d3615e..9740dd031 100644 --- a/MongoDB/samples/ReplicaSet/src/ReplicaSetMonitor.cpp +++ b/MongoDB/samples/ReplicaSet/src/ReplicaSetMonitor.cpp @@ -42,6 +42,7 @@ using namespace Poco; struct MonitorConfig { + std::string uri; std::string setName; std::vector 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 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"); } diff --git a/MongoDB/src/ReplicaSet.cpp b/MongoDB/src/ReplicaSet.cpp index 97835d8e7..6235de117 100644 --- a/MongoDB/src/ReplicaSet.cpp +++ b/MongoDB/src/ReplicaSet.cpp @@ -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 @@ -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 + } }