summaryrefslogtreecommitdiff
path: root/jstests/free_mon
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/free_mon')
-rw-r--r--jstests/free_mon/free_mon_announce.js35
-rw-r--r--jstests/free_mon/free_mon_disable.js35
-rw-r--r--jstests/free_mon/free_mon_http_down.js32
-rw-r--r--jstests/free_mon/free_mon_http_validate.js35
-rw-r--r--jstests/free_mon/free_mon_metrics_halt.js29
-rw-r--r--jstests/free_mon/free_mon_metrics_perm_del.js35
-rw-r--r--jstests/free_mon/free_mon_register.js53
-rw-r--r--jstests/free_mon/free_mon_register_cmd.js74
-rw-r--r--jstests/free_mon/free_mon_register_off.js40
-rw-r--r--jstests/free_mon/free_mon_register_resend.js28
-rw-r--r--jstests/free_mon/free_mon_rs_corrupt.js37
-rw-r--r--jstests/free_mon/free_mon_rs_delete.js66
-rw-r--r--jstests/free_mon/free_mon_rs_halt.js62
-rw-r--r--jstests/free_mon/free_mon_rs_off.js38
-rw-r--r--jstests/free_mon/free_mon_rs_perm_del.js55
-rw-r--r--jstests/free_mon/free_mon_rs_register.js95
-rw-r--r--jstests/free_mon/free_mon_rs_resend.js66
-rw-r--r--jstests/free_mon/free_mon_server_status.js49
-rw-r--r--jstests/free_mon/libs/free_mon.js331
-rw-r--r--jstests/free_mon/libs/mock_http_common.py24
-rw-r--r--jstests/free_mon/libs/mock_http_control.py52
-rw-r--r--jstests/free_mon/libs/mock_http_server.py306
22 files changed, 1577 insertions, 0 deletions
diff --git a/jstests/free_mon/free_mon_announce.js b/jstests/free_mon/free_mon_announce.js
new file mode 100644
index 00000000000..d78d0b58608
--- /dev/null
+++ b/jstests/free_mon/free_mon_announce.js
@@ -0,0 +1,35 @@
+// Validate connect message display.
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+const mock_web = new FreeMonWebServer();
+mock_web.start();
+
+const mongod = MongoRunner.runMongod({
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+});
+assert.neq(mongod, null, 'mongod not running');
+const admin = mongod.getDB('admin');
+
+function getConnectAnnounce() {
+ // Capture message as it'd be presented to a user.
+ clearRawMongoProgramOutput();
+ const exitCode = runMongoProgram(
+ 'mongo', '--port', mongod.port, '--eval', "shellHelper( 'show', 'freeMonitoring' );");
+ assert.eq(exitCode, 0);
+ return rawMongoProgramOutput();
+}
+
+// state === 'enabled'.
+admin.enableFreeMonitoring();
+WaitForRegistration(mongod);
+const reminder = "To see your monitoring data";
+assert.neq(getConnectAnnounce().search(reminder), -1, 'userReminder not found');
+
+// Cleanup.
+MongoRunner.stopMongod(mongod);
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_disable.js b/jstests/free_mon/free_mon_disable.js
new file mode 100644
index 00000000000..4bbc2236407
--- /dev/null
+++ b/jstests/free_mon/free_mon_disable.js
@@ -0,0 +1,35 @@
+// Validate disable works
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ freeMonitoringTag: "foo",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+assert.commandWorked(conn.adminCommand({setFreeMonitoring: 1, action: "disable"}));
+
+const stats = mock_web.queryStats();
+print(tojson(stats));
+
+assert.eq(stats.registers, 0);
+
+assert.eq(FreeMonGetStatus(conn).state, "disabled");
+
+assert.eq(FreeMonGetServerStatus(conn).state, "disabled");
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_http_down.js b/jstests/free_mon/free_mon_http_down.js
new file mode 100644
index 00000000000..6e7226e8732
--- /dev/null
+++ b/jstests/free_mon/free_mon_http_down.js
@@ -0,0 +1,32 @@
+// Validate registration retries if the web server is down.
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_FAIL_REGISTER);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+const admin = conn.getDB('admin');
+
+mock_web.waitRegisters(3);
+
+assert.soon(function() {
+ const freeMonStats = FreeMonGetServerStatus(conn);
+ return freeMonStats.registerErrors >= 3;
+}, "Failed to wait for 3 register errors: " + FreeMonGetServerStatus(conn), 20 * 1000);
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_http_validate.js b/jstests/free_mon/free_mon_http_validate.js
new file mode 100644
index 00000000000..30f521903cf
--- /dev/null
+++ b/jstests/free_mon/free_mon_http_validate.js
@@ -0,0 +1,35 @@
+// Ensure free monitoring gives up if registration fails
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_INVALID_REGISTER);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+mock_web.waitRegisters(1);
+
+// Sleep for some more time in case free monitoring would still try to register
+sleep(20 * 1000);
+
+// Ensure it only tried to register once since we gave it a bad response.
+const stats = mock_web.queryStats();
+print(tojson(stats));
+
+assert.eq(stats.registers, 1);
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_metrics_halt.js b/jstests/free_mon/free_mon_metrics_halt.js
new file mode 100644
index 00000000000..ec1c1248801
--- /dev/null
+++ b/jstests/free_mon/free_mon_metrics_halt.js
@@ -0,0 +1,29 @@
+// Ensure free monitoring gives up if metrics returns halt
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_HALT_METRICS_5);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+mock_web.waitMetrics(6);
+
+// It gets marked as disabled on halt
+WaitForUnRegistration(conn);
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_metrics_perm_del.js b/jstests/free_mon/free_mon_metrics_perm_del.js
new file mode 100644
index 00000000000..b10d8d97f25
--- /dev/null
+++ b/jstests/free_mon/free_mon_metrics_perm_del.js
@@ -0,0 +1,35 @@
+// Ensure free monitoring gives up if metrics returns permanently delete
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_PERMANENTLY_DELETE_AFTER_3);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+mock_web.waitMetrics(4);
+
+// Make sure the registration document gets removed
+assert.soon(
+ function() {
+ const reg = FreeMonGetRegistration(conn);
+ return reg === undefined;
+ },
+ "Failed to wait for free mon document to be removed: " + FreeMonGetRegistration(conn),
+ 20 * 1000);
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_register.js b/jstests/free_mon/free_mon_register.js
new file mode 100644
index 00000000000..19bb2e59244
--- /dev/null
+++ b/jstests/free_mon/free_mon_register.js
@@ -0,0 +1,53 @@
+// Validate registration works
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+const localTime = Date.now();
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ freeMonitoringTag: "foo",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+WaitForRegistration(conn);
+
+const stats = mock_web.queryStats();
+print(tojson(stats));
+
+assert.eq(stats.registers, 1);
+
+const last_register = mock_web.query("last_register");
+print(tojson(last_register));
+
+assert.eq(last_register.version, 2);
+assert.gt(new Date().setTime(last_register.localTime["$date"]), localTime);
+assert.eq(last_register.payload.buildInfo.bits, 64);
+assert.eq(last_register.payload.buildInfo.ok, 1);
+assert.eq(last_register.payload.storageEngine.readOnly, false);
+assert.eq(last_register.payload.isMaster.ok, 1);
+assert.eq(last_register.tags, ["foo"]);
+
+mock_web.waitMetrics(2);
+
+const last_metrics = mock_web.query("last_metrics");
+print(tojson(last_metrics));
+
+assert.eq(last_metrics.version, 2);
+assert.gt(new Date().setTime(last_metrics.localTime["$date"]), localTime);
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_register_cmd.js b/jstests/free_mon/free_mon_register_cmd.js
new file mode 100644
index 00000000000..295908663a5
--- /dev/null
+++ b/jstests/free_mon/free_mon_register_cmd.js
@@ -0,0 +1,74 @@
+// Validate registration works via a command. Validate it can be registered and unregistered
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+// Wait an arbitrary amount of time to allow the processor loop to start.
+sleep(10 * 1000);
+
+// Then verify that no registrations happened since we haven't runtime enabled yed.
+assert.eq('undecided',
+ conn.getDB('admin').getFreeMonitoringStatus().state,
+ "Initial state should be 'undecided'");
+assert.eq(0, mock_web.queryStats().registers, "mongod registered without enabling free_mod");
+
+assert.commandWorked(conn.adminCommand({setFreeMonitoring: 1, action: "enable"}));
+
+WaitForFreeMonServerStatusState(conn, 'enabled');
+
+// The command should either timeout or suceed after registration is complete
+const retStatus1 = conn.adminCommand({getFreeMonitoringStatus: 1});
+assert.commandWorked(retStatus1);
+assert.eq(retStatus1.state, "enabled", tojson(retStatus1));
+
+const stats = mock_web.queryStats();
+print(tojson(stats));
+
+assert.eq(stats.registers, 1);
+
+const last_register = mock_web.query("last_register");
+print(tojson(last_register));
+
+assert.eq(last_register.version, 2);
+assert.eq(last_register.payload.buildInfo.bits, 64);
+assert.eq(last_register.payload.buildInfo.ok, 1);
+assert.eq(last_register.payload.storageEngine.readOnly, false);
+assert.eq(last_register.payload.isMaster.ok, 1);
+
+mock_web.waitMetrics(2);
+
+const last_metrics = mock_web.query("last_metrics");
+print(tojson(last_metrics));
+
+assert.eq(last_metrics.version, 2);
+
+assert.commandWorked(conn.adminCommand({setFreeMonitoring: 1, action: "disable"}));
+
+// Wait for unregistration to occur
+assert.soon(function() {
+ const regDoc = FreeMonGetRegistration(conn);
+ return regDoc.state == "disabled";
+}, "Failed to unregister", 60 * 1000);
+
+const retStatus2 = conn.adminCommand({getFreeMonitoringStatus: 1});
+assert.commandWorked(retStatus2);
+assert.eq(retStatus2.state, "disabled", tojson(retStatus1));
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_register_off.js b/jstests/free_mon/free_mon_register_off.js
new file mode 100644
index 00000000000..591f95f0a09
--- /dev/null
+++ b/jstests/free_mon/free_mon_register_off.js
@@ -0,0 +1,40 @@
+// Validate registration does work if free monitoring is disabled.
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "off",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+assert.commandFailed(conn.adminCommand({setFreeMonitoring: 1, action: "enable"}));
+
+// If it some time in case it actually started to process something.
+sleep(10 * 1000);
+
+const retStatus1 = conn.adminCommand({getFreeMonitoringStatus: 1});
+assert.commandWorked(retStatus1);
+assert.eq(retStatus1.state, "disabled", tojson(retStatus1));
+
+const stats = mock_web.queryStats();
+print(tojson(stats));
+
+assert.eq(stats.registers, 0);
+
+assert.commandFailed(conn.adminCommand({setFreeMonitoring: 1, action: "disable"}));
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_register_resend.js b/jstests/free_mon/free_mon_register_resend.js
new file mode 100644
index 00000000000..ab9af561a4d
--- /dev/null
+++ b/jstests/free_mon/free_mon_register_resend.js
@@ -0,0 +1,28 @@
+// Validate resend registration works in a replica set
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_RESEND_REGISTRATION_ONCE);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up');
+
+WaitForRegistration(conn);
+
+mock_web.waitRegisters(2);
+
+MongoRunner.stopMongod(conn);
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_corrupt.js b/jstests/free_mon/free_mon_rs_corrupt.js
new file mode 100644
index 00000000000..c8e3099447c
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_corrupt.js
@@ -0,0 +1,37 @@
+// Validate a user manipulating system.version for free monitoring does
+// not crash mongod
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const rst = new ReplSetTest({nodes: 2, nodeOptions: options});
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+WaitForRegistration(rst.getPrimary());
+
+mock_web.waitRegisters(2);
+
+// For kicks, corrupt the free monitoring storage state to knock free mon offline
+// and make sure the node does not crash
+rst.getPrimary().getDB("admin").system.version.update({_id: "free_monitoring"},
+ {$set: {version: 2}});
+
+sleep(20 * 1000);
+
+rst.stopSet();
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_delete.js b/jstests/free_mon/free_mon_rs_delete.js
new file mode 100644
index 00000000000..0d043c394c1
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_delete.js
@@ -0,0 +1,66 @@
+// Validate a user deleting free monitoring in system.version does
+// not crash mongod
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const rst = new ReplSetTest({nodes: 2, nodeOptions: options});
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+WaitForRegistration(rst.getPrimary());
+
+mock_web.waitRegisters(2);
+
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+
+const qs1 = mock_web.queryStats();
+
+jsTestLog("Breaking Free Monitoring");
+
+// For kicks, delete the free monitoring storage state to knock free mon offline
+// and make sure the node does not crash
+rst.getPrimary().getDB("admin").system.version.remove({_id: "free_monitoring"});
+
+jsTestLog("Sleeping for 20s");
+sleep(20 * 1000);
+
+const qs2 = mock_web.queryStats();
+
+// Make sure we are back to the initial state.
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'undecided');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'undecided');
+
+// Verify free monitoring stops but tolerate one additional collection
+assert.gte(qs1.metrics + 2, qs2.metrics);
+// Tolerate an additional registration on the secondary side. We may delete the record on the
+// primary before the secondary processes all pending registrations requests.
+assert.lte(qs1.registers, qs2.registers);
+
+// Enable it again to be sure we can resume
+assert.commandWorked(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "enable"}));
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+
+sleep(20 * 1000);
+
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+
+rst.stopSet();
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_halt.js b/jstests/free_mon/free_mon_rs_halt.js
new file mode 100644
index 00000000000..e89a855a424
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_halt.js
@@ -0,0 +1,62 @@
+// Validate that if the endpoint returns halt = true that free monitoring halts
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_HALT_METRICS_5, true);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const rst = new ReplSetTest({nodes: 2, nodeOptions: options});
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+WaitForRegistration(rst.getPrimary());
+
+mock_web.waitRegisters(2);
+
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'enabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'enabled');
+
+mock_web.enableFaults();
+mock_web.waitFaults(1);
+
+const qs1 = mock_web.queryStats();
+
+sleep(20 * 1000);
+
+const qs2 = mock_web.queryStats();
+
+// Verify free monitoring stops but tolerate one additional collection
+assert.gte(qs1.metrics + 1, qs2.metrics);
+assert.eq(qs1.registers, qs2.registers);
+
+// Halt causes us to disable free monitoring, not return it to initial state.
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'disabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'disabled');
+
+// Disable the fault so we can re-enable again
+mock_web.disableFaults();
+
+// Enable it again to be sure we can resume
+assert.commandWorked(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "enable"}));
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+
+sleep(20 * 1000);
+
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'enabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'enabled');
+
+rst.stopSet();
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_off.js b/jstests/free_mon/free_mon_rs_off.js
new file mode 100644
index 00000000000..2b4d34fc87e
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_off.js
@@ -0,0 +1,38 @@
+// Validate replica set starts up with free monitoring disabled
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "off",
+ verbose: 1,
+};
+
+const rst = new ReplSetTest({nodes: 2, nodeOptions: options});
+
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+const retStatus1 = rst.getPrimary().adminCommand({getFreeMonitoringStatus: 1});
+assert.commandWorked(retStatus1);
+assert.eq(retStatus1.state, "disabled", tojson(retStatus1));
+
+const stats = mock_web.queryStats();
+print(tojson(stats));
+
+assert.eq(stats.registers, 0);
+
+assert.commandFailed(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "disable"}));
+
+rst.stopSet();
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_perm_del.js b/jstests/free_mon/free_mon_rs_perm_del.js
new file mode 100644
index 00000000000..182880ff921
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_perm_del.js
@@ -0,0 +1,55 @@
+// Validate that if the endpoint says permanently delete that the state
+// document is deleted and replicated properly
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_PERMANENTLY_DELETE_AFTER_3, true);
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ enableFreeMonitoring: "on",
+ verbose: 1,
+};
+
+const rst = new ReplSetTest({nodes: 2, nodeOptions: options});
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+WaitForRegistration(rst.getPrimary());
+
+mock_web.waitRegisters(2);
+
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+
+mock_web.enableFaults();
+mock_web.waitFaults(1);
+
+sleep(20 * 1000);
+
+// Make sure we are back to the initial state.
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'undecided');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'undecided');
+
+// Disable the fault so we can re-enable again
+mock_web.disableFaults();
+
+// Enable it again to be sure we can resume
+assert.commandWorked(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "enable"}));
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+
+sleep(20 * 1000);
+
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'enabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'enabled');
+
+rst.stopSet();
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_register.js b/jstests/free_mon/free_mon_rs_register.js
new file mode 100644
index 00000000000..1c312d5fb4c
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_register.js
@@ -0,0 +1,95 @@
+// Validate registration works in a replica set
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer();
+
+mock_web.start();
+
+let options = {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ verbose: 1,
+};
+
+const rst = new ReplSetTest({nodes: 2, nodeOptions: options});
+
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+sleep(10 * 1000);
+assert.eq(0, mock_web.queryStats().registers, "mongod registered without enabling free_mod");
+
+assert.commandWorked(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "enable"}));
+WaitForRegistration(rst.getPrimary());
+
+mock_web.waitRegisters(2);
+
+WaitForRegistration(rst.getPrimary());
+WaitForRegistration(rst.getSecondary());
+ValidateFreeMonReplicaSet(rst);
+
+const last_register = mock_web.query("last_register");
+print(tojson(last_register));
+
+assert.eq(last_register.version, 2);
+assert.eq(last_register.payload.buildInfo.bits, 64);
+assert.eq(last_register.payload.buildInfo.ok, 1);
+assert.eq(last_register.payload.storageEngine.readOnly, false);
+assert.eq(last_register.payload.isMaster.ok, 1);
+assert.gte(last_register.payload.replSetGetConfig.config.version, 2);
+
+function isUUID(val) {
+ // Mock webserver gives us back unpacked BinData/UUID in the form:
+ // { '$uuid': '0123456789abcdef0123456789abcdef' }.
+ if ((typeof val) !== 'object') {
+ return false;
+ }
+ const uuid = val['$uuid'];
+ if ((typeof uuid) !== 'string') {
+ return false;
+ }
+ return uuid.match(/^[0-9a-fA-F]{32}$/) !== null;
+}
+assert.eq(isUUID(last_register.payload.uuid['local.oplog.rs']), true);
+
+// Restart the secondary
+var s1 = rst.getSecondary();
+var s1Id = rst.getNodeId(s1);
+
+rst.stop(s1Id);
+rst.waitForState(s1, ReplSetTest.State.DOWN);
+
+rst.restart(s1Id);
+
+mock_web.waitRegisters(3);
+
+// Now disable it
+assert.commandWorked(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "disable"}));
+
+WaitForUnRegistration(rst.getPrimary());
+WaitForUnRegistration(rst.getSecondary());
+
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'disabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'disabled');
+
+// Restart the secondary with it disabled
+var s1 = rst.getSecondary();
+var s1Id = rst.getNodeId(s1);
+
+rst.stop(s1Id);
+rst.waitForState(s1, ReplSetTest.State.DOWN);
+
+rst.restart(s1Id);
+
+// Make sure it is disabled
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'disabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'disabled');
+
+rst.stopSet();
+
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/free_mon_rs_resend.js b/jstests/free_mon/free_mon_rs_resend.js
new file mode 100644
index 00000000000..bdf7602c871
--- /dev/null
+++ b/jstests/free_mon/free_mon_rs_resend.js
@@ -0,0 +1,66 @@
+// Validate resend registration works in a replica set
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+let mock_web = new FreeMonWebServer(FAULT_RESEND_REGISTRATION_AT_3);
+let mock_web_sec = new FreeMonWebServer(FAULT_RESEND_REGISTRATION_ONCE, true);
+
+mock_web.start();
+mock_web_sec.start();
+
+const rst = new ReplSetTest({
+ name: "free_mon_rs_register",
+ nodes: [
+ {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+ verbose: 1,
+ },
+ {
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web_sec.getURL(),
+ verbose: 1,
+ }
+ ]
+});
+
+rst.startSet();
+rst.initiate();
+rst.awaitReplication();
+
+sleep(10 * 1000);
+assert.eq(0, mock_web.queryStats().registers, "mongod registered without enabling free_mod");
+assert.eq(0, mock_web_sec.queryStats().registers, "mongod registered without enabling free_mod");
+
+assert.commandWorked(rst.getPrimary().adminCommand({setFreeMonitoring: 1, action: "enable"}));
+WaitForRegistration(rst.getPrimary());
+
+mock_web.waitRegisters(1);
+mock_web_sec.waitRegisters(1);
+
+WaitForFreeMonServerStatusState(rst.getPrimary(), 'enabled');
+WaitForFreeMonServerStatusState(rst.getSecondary(), 'enabled');
+ValidateFreeMonReplicaSet(rst);
+
+mock_web.waitRegisters(2);
+mock_web_sec.waitRegisters(2);
+mock_web_sec.disableFaults();
+
+// Trigger resend on the secondary only
+mock_web_sec.enableFaults();
+mock_web_sec.waitFaults(1);
+mock_web_sec.waitRegisters(3);
+
+// Double check registers were as expected
+const stats = mock_web.queryStats();
+assert.eq(stats.registers, 2);
+
+const stats_sec = mock_web_sec.queryStats();
+assert.gte(stats_sec.registers, 3);
+
+rst.stopSet();
+
+mock_web.stop();
+mock_web_sec.stop();
+})();
diff --git a/jstests/free_mon/free_mon_server_status.js b/jstests/free_mon/free_mon_server_status.js
new file mode 100644
index 00000000000..5d9848affb8
--- /dev/null
+++ b/jstests/free_mon/free_mon_server_status.js
@@ -0,0 +1,49 @@
+// Validate serverStatus output.
+//
+load("jstests/free_mon/libs/free_mon.js");
+
+(function() {
+'use strict';
+
+const mock_web = new FreeMonWebServer();
+mock_web.start();
+
+const mongod = MongoRunner.runMongod({
+ setParameter: "cloudFreeMonitoringEndpointURL=" + mock_web.getURL(),
+});
+assert.neq(mongod, null, 'mongod not running');
+const admin = mongod.getDB('admin');
+
+const kRetryIntervalSecs = 1;
+function freeMonStats() {
+ return assert.commandWorked(admin.runCommand({serverStatus: 1})).freeMonitoring;
+}
+
+// Initial state.
+assert.eq(freeMonStats().state, 'undecided');
+
+admin.enableFreeMonitoring();
+WaitForRegistration(mongod);
+
+// Enabled.
+const enabled = freeMonStats();
+assert.eq(enabled.state, 'enabled');
+assert.eq(enabled.retryIntervalSecs, kRetryIntervalSecs);
+assert.eq(enabled.registerErrors, 0);
+assert.eq(enabled.metricsErrors, 0);
+
+// Explicitly disabled.
+admin.disableFreeMonitoring();
+
+WaitForFreeMonServerStatusState(mongod, "disabled");
+
+const disabled = freeMonStats();
+assert.eq(disabled.state, 'disabled');
+assert.eq(disabled.retryIntervalSecs, kRetryIntervalSecs);
+assert.eq(disabled.registerErrors, 0);
+assert.eq(disabled.metricsErrors, 0);
+
+// Cleanup.
+MongoRunner.stopMongod(mongod);
+mock_web.stop();
+})();
diff --git a/jstests/free_mon/libs/free_mon.js b/jstests/free_mon/libs/free_mon.js
new file mode 100644
index 00000000000..269f1e65306
--- /dev/null
+++ b/jstests/free_mon/libs/free_mon.js
@@ -0,0 +1,331 @@
+
+/**
+ * Control the Free Monitoring Mock Webserver.
+ */
+
+// These faults must match the list of faults in mock_http_server.py, see the
+// SUPPORTED_FAULT_TYPES list in mock_http_server.py
+const FAULT_FAIL_REGISTER = "fail_register";
+const FAULT_INVALID_REGISTER = "invalid_register";
+const FAULT_HALT_METRICS_5 = "halt_metrics_5";
+const FAULT_PERMANENTLY_DELETE_AFTER_3 = "permanently_delete_after_3";
+const FAULT_RESEND_REGISTRATION_AT_3 = "resend_registration_at_3";
+const FAULT_RESEND_REGISTRATION_ONCE = "resend_registration_once";
+
+const DISABLE_FAULTS = "disable_faults";
+const ENABLE_FAULTS = "enable_faults";
+
+class FreeMonWebServer {
+ /**
+ * Create a new webserver.
+ *
+ * @param {string} fault_type
+ * @param {bool} disableFaultsOnStartup optionally disable fault on startup
+ */
+ constructor(fault_type, disableFaultsOnStartup) {
+ this.python = "python3";
+ this.disableFaultsOnStartup = disableFaultsOnStartup || false;
+ this.fault_type = fault_type;
+
+ if (_isWindows()) {
+ this.python = "python.exe";
+ }
+
+ print("Using python interpreter: " + this.python);
+ this.web_server_py = "jstests/free_mon/libs/mock_http_server.py";
+ this.control_py = "jstests/free_mon/libs/mock_http_control.py";
+
+ this.pid = undefined;
+ this.port = -1;
+ }
+
+ /**
+ * Get the Port.
+ *
+ * @return {number} port number of http server
+ */
+ getPort() {
+ return port;
+ }
+
+ /**
+ * Get the URL.
+ *
+ * @return {string} url of http server
+ */
+ getURL() {
+ return "http://localhost:" + this.port;
+ }
+
+ /**
+ * Start the Mock HTTP Server.
+ */
+ start() {
+ this.port = allocatePort();
+ print("Mock Web server is listening on port: " + this.port);
+
+ let args = [this.python, "-u", this.web_server_py, "--port=" + this.port];
+ if (this.fault_type) {
+ args.push("--fault=" + this.fault_type);
+ if (this.disableFaultsOnStartup) {
+ args.push("--disable-faults");
+ }
+ }
+
+ clearRawMongoProgramOutput();
+
+ this.pid = _startMongoProgram({args: args});
+
+ assert(checkProgram(this.pid));
+
+ // Wait for the web server to start
+ assert.soon(function() {
+ return rawMongoProgramOutput().search("Mock Web Server Listening") !== -1;
+ });
+
+ print("Mock HTTP Server sucessfully started.");
+ }
+
+ /**
+ * Stop the Mock HTTP Server.
+ */
+ stop() {
+ stopMongoProgramByPid(this.pid);
+ }
+
+ /**
+ * Query the HTTP server.
+ *
+ * @param {string} query type
+ *
+ * @return {object} Object representation of JSON from the server.
+ */
+ query(query) {
+ const out_file = "out_" + this.port + ".txt";
+ const python_command = this.python + " -u " + this.control_py + " --port=" + this.port +
+ " --query=" + query + " > " + out_file;
+
+ let ret = 0;
+ if (_isWindows()) {
+ ret = runProgram('cmd.exe', '/c', python_command);
+ } else {
+ ret = runProgram('/bin/sh', '-c', python_command);
+ }
+
+ assert.eq(ret, 0);
+
+ const result = cat(out_file);
+
+ try {
+ return JSON.parse(result);
+ } catch (e) {
+ jsTestLog("Failed to parse: " + result + "\n" + result);
+ throw e;
+ }
+ }
+
+ /**
+ * Control the HTTP server.
+ *
+ * @param {string} query type
+ */
+ control(query) {
+ const out_file = "out_" + this.port + ".txt";
+ const python_command = this.python + " -u " + this.control_py + " --port=" + this.port +
+ " --query=" + query + " > " + out_file;
+
+ let ret = 0;
+ if (_isWindows()) {
+ ret = runProgram('cmd.exe', '/c', python_command);
+ } else {
+ ret = runProgram('/bin/sh', '-c', python_command);
+ }
+
+ assert.eq(ret, 0);
+ }
+
+ /**
+ * Disable Faults
+ */
+ disableFaults() {
+ this.control(DISABLE_FAULTS);
+ }
+
+ /**
+ * Enable Faults
+ */
+ enableFaults() {
+ this.control(ENABLE_FAULTS);
+ }
+
+ /**
+ * Query the stats page for the HTTP server.
+ *
+ * @return {object} Object representation of JSON from the server.
+ */
+ queryStats() {
+ return this.query("stats");
+ }
+
+ /**
+ * Wait for N register calls to be received by web server.
+ *
+ * @throws assert.soon() exception
+ */
+ waitRegisters(count) {
+ const qs = this.queryStats.bind(this);
+ const port = this.port;
+ // Wait for registration to occur
+ assert.soon(function() {
+ const stats = qs();
+ print("w" + port + "| waiting for registers >= (" + count + ") QS : " + tojson(stats));
+ return stats.registers >= count;
+ }, "Failed to web server register", 60 * 1000);
+ }
+
+ /**
+ * Wait for N metrics calls to be received by web server.
+ *
+ * @throws assert.soon() exception
+ */
+ waitMetrics(count) {
+ const qs = this.queryStats.bind(this);
+ const port = this.port;
+ // Wait for metrics uploads to occur
+ assert.soon(function() {
+ const stats = qs();
+ print("w" + port + "| waiting for metrics >= (" + count + ") QS : " + tojson(stats));
+ return stats.metrics >= count;
+ }, "Failed to web server metrics", 60 * 1000);
+ }
+
+ /**
+ * Wait for N fault calls to e received by web server.
+ *
+ * @throws assert.soon() exception
+ */
+ waitFaults(count) {
+ const qs = this.queryStats.bind(this);
+ const port = this.port;
+ // Wait for faults to be triggered
+ assert.soon(function() {
+ const stats = qs();
+ print("w" + port + "| waiting for faults >= (" + count + ") QS : " + tojson(stats));
+ return stats.faults >= count;
+ }, "Failed to web server faults", 60 * 1000);
+ }
+}
+
+/**
+ * Wait for registration information to be populated in the database.
+ *
+ * @param {object} conn
+ * @param {string} state
+ */
+function WaitForDiskState(conn, state) {
+ 'use strict';
+
+ const admin = conn.getDB("admin");
+
+ // Wait for registration to occur
+ assert.soon(function() {
+ const docs = admin.system.version.find({_id: "free_monitoring"});
+ const da = docs.toArray();
+ return da.length === 1 && da[0].state === state;
+ }, "Failed to disk state", 60 * 1000);
+}
+
+/**
+ * Wait for registration information to be populated in the database.
+ *
+ * @param {object} conn
+ */
+function WaitForRegistration(conn) {
+ WaitForDiskState(conn, 'enabled');
+}
+
+/**
+ * Wait for unregistration information to be populated in the database.
+ *
+ * @param {object} conn
+ */
+function WaitForUnRegistration(conn) {
+ WaitForDiskState(conn, 'disabled');
+}
+
+/**
+ * Get registration document.
+ *
+ * @param {object} registration document
+ */
+function FreeMonGetRegistration(conn) {
+ 'use strict';
+
+ const admin = conn.getDB("admin");
+ const docs = admin.system.version.find({_id: "free_monitoring"});
+ const da = docs.toArray();
+ return da[0];
+}
+
+/**
+ * Get current Free Monitoring Status via serverStatus.
+ *
+ * @param {object} serverStatus.freeMonitoring section
+ */
+function FreeMonGetServerStatus(conn) {
+ 'use strict';
+
+ const admin = conn.getDB("admin");
+ return assert.commandWorked(admin.runCommand({serverStatus: 1})).freeMonitoring;
+}
+
+/**
+ * Get current Free Monitoring Status via getFreeMonitoringStatus.
+ *
+ * @param {object} getFreeMonitoringStatus document
+ */
+function FreeMonGetStatus(conn) {
+ 'use strict';
+
+ const admin = conn.getDB("admin");
+ return assert.commandWorked(admin.runCommand({getFreeMonitoringStatus: 1}));
+}
+
+/**
+ * Wait for server status state
+ *
+ * @param {object} conn
+ * @param {string} state
+ */
+function WaitForFreeMonServerStatusState(conn, state) {
+ 'use strict';
+
+ // Wait for registration to occur
+ assert.soon(
+ function() {
+ let status = FreeMonGetServerStatus(conn).state;
+ return status === state;
+ },
+ "Failed to find expected server status state: expected: '" + state +
+ "', actual: " + tojson(FreeMonGetServerStatus(conn)),
+ 20 * 1000);
+}
+
+/**
+ * Validate Free Monitoring Replica Set consistency
+ * WARNING: Not valid if secondary is started with enableFreeMonitoring since it registers before it
+ * joins the replica set.
+ *
+ * @param {object} rst
+ */
+function ValidateFreeMonReplicaSet(rst) {
+ 'use strict';
+
+ const primary_status = FreeMonGetStatus(rst.getPrimary());
+ const primary_url = primary_status.url;
+ const secondary_status = FreeMonGetStatus(rst.getSecondary());
+ const secondary_url = secondary_status.url;
+ assert.eq(primary_url,
+ secondary_url,
+ `DUMP ${tojson(primary_status)} == ${tojson(secondary_status)}`);
+}
diff --git a/jstests/free_mon/libs/mock_http_common.py b/jstests/free_mon/libs/mock_http_common.py
new file mode 100644
index 00000000000..14cc6ba5a21
--- /dev/null
+++ b/jstests/free_mon/libs/mock_http_common.py
@@ -0,0 +1,24 @@
+"""Common code for mock free monitoring http endpoint."""
+import json
+
+URL_PATH_STATS = "/stats"
+URL_PATH_LAST_REGISTER = "/last_register"
+URL_PATH_LAST_METRICS = "/last_metrics"
+URL_DISABLE_FAULTS = "/disable_faults"
+URL_ENABLE_FAULTS = "/enable_faults"
+
+
+class Stats:
+ """Stats class shared between client and server."""
+
+ def __init__(self):
+ self.register_calls = 0
+ self.metrics_calls = 0
+ self.fault_calls = 0
+
+ def __repr__(self):
+ return json.dumps({
+ 'metrics': self.metrics_calls,
+ 'registers': self.register_calls,
+ 'faults': self.fault_calls,
+ })
diff --git a/jstests/free_mon/libs/mock_http_control.py b/jstests/free_mon/libs/mock_http_control.py
new file mode 100644
index 00000000000..778450dd374
--- /dev/null
+++ b/jstests/free_mon/libs/mock_http_control.py
@@ -0,0 +1,52 @@
+#! /usr/bin/env python3
+"""
+Python script to interact with mock free monitoring HTTP server.
+"""
+
+import argparse
+import json
+import logging
+import sys
+import urllib.request
+
+import mock_http_common
+
+
+def main():
+ """Main entry point."""
+ parser = argparse.ArgumentParser(description='MongoDB Mock Free Monitoring Endpoint.')
+
+ parser.add_argument('-p', '--port', type=int, default=8000, help="Port to listen on")
+
+ parser.add_argument('-v', '--verbose', action='count', help="Enable verbose tracing")
+
+ parser.add_argument('--query', type=str, help="Query endpoint <name>")
+
+ args = parser.parse_args()
+ if args.verbose:
+ logging.basicConfig(level=logging.DEBUG)
+
+ url_str = "http://localhost:" + str(args.port)
+ if args.query == "stats":
+ url_str += mock_http_common.URL_PATH_STATS
+ elif args.query == "last_register":
+ url_str += mock_http_common.URL_PATH_LAST_REGISTER
+ elif args.query == "last_metrics":
+ url_str += mock_http_common.URL_PATH_LAST_METRICS
+ elif args.query == "disable_faults":
+ url_str += mock_http_common.URL_DISABLE_FAULTS
+ elif args.query == "enable_faults":
+ url_str += mock_http_common.URL_ENABLE_FAULTS
+ else:
+ print("Unknown query type")
+ sys.exit(1)
+
+ with urllib.request.urlopen(url_str) as f:
+ print(f.read().decode('utf-8'))
+
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+
+ main()
diff --git a/jstests/free_mon/libs/mock_http_server.py b/jstests/free_mon/libs/mock_http_server.py
new file mode 100644
index 00000000000..7bfd8974c59
--- /dev/null
+++ b/jstests/free_mon/libs/mock_http_server.py
@@ -0,0 +1,306 @@
+#! /usr/bin/env python3
+"""Mock Free Monitoring Endpoint."""
+
+import argparse
+import collections
+import http.server
+import json
+import logging
+import socketserver
+import sys
+import urllib.parse
+
+import bson
+from bson.codec_options import CodecOptions
+from bson.json_util import dumps
+import mock_http_common
+
+# Pass this data out of band instead of storing it in FreeMonHandler since the
+# BaseHTTPRequestHandler does not call the methods as object methods but as class methods. This
+# means there is not self.
+stats = mock_http_common.Stats()
+last_metrics = None
+last_register = None
+disable_faults = False
+fault_type = None
+"""Fault which causes the server to return an HTTP failure on register."""
+FAULT_FAIL_REGISTER = "fail_register"
+"""Fault which causes the server to return a response with a document with a bad version."""
+FAULT_INVALID_REGISTER = "invalid_register"
+"""Fault which causes metrics to return halt after 5 metric uploads have occurred."""
+FAULT_HALT_METRICS_5 = "halt_metrics_5"
+"""Fault which causes metrics to return permanentlyDelete = true after 3 uploads."""
+FAULT_PERMANENTLY_DELETE_AFTER_3 = "permanently_delete_after_3"
+"""Fault which causes metrics to trigger resentRegistration at 3 uploads."""
+FAULT_RESEND_REGISTRATION_AT_3 = "resend_registration_at_3"
+"""Fault which causes metrics to trigger resentRegistration once."""
+FAULT_RESEND_REGISTRATION_ONCE = "resend_registration_once"
+
+# List of supported fault types
+SUPPORTED_FAULT_TYPES = [
+ FAULT_FAIL_REGISTER,
+ FAULT_INVALID_REGISTER,
+ FAULT_HALT_METRICS_5,
+ FAULT_PERMANENTLY_DELETE_AFTER_3,
+ FAULT_RESEND_REGISTRATION_AT_3,
+ FAULT_RESEND_REGISTRATION_ONCE,
+]
+
+# Supported POST URL types
+URL_POST_REGISTER = '/register'
+URL_POST_METRICS = '/metrics'
+
+
+class FreeMonHandler(http.server.BaseHTTPRequestHandler):
+ """
+ Handle requests from Free Monitoring and test commands
+ """
+
+ def do_GET(self):
+ """Serve a Test GET request."""
+ parts = urllib.parse.urlsplit(self.path)
+ path = parts[2]
+
+ if path == mock_http_common.URL_PATH_STATS:
+ self._do_stats()
+ elif path == mock_http_common.URL_PATH_LAST_REGISTER:
+ self._do_last_register()
+ elif path == mock_http_common.URL_PATH_LAST_METRICS:
+ self._do_last_metrics()
+ elif path == mock_http_common.URL_DISABLE_FAULTS:
+ self._do_disable_faults()
+ elif path == mock_http_common.URL_ENABLE_FAULTS:
+ self._do_enable_faults()
+ else:
+ self.send_response(http.HTTPStatus.NOT_FOUND)
+ self.end_headers()
+ self.wfile.write("Unknown URL".encode())
+
+ def do_POST(self):
+ """Serve a Free Monitoring POST request."""
+ parts = urllib.parse.urlsplit(self.path)
+ path = parts[2]
+
+ if path == URL_POST_REGISTER:
+ self._do_registration()
+ elif path == URL_POST_METRICS:
+ self._do_metrics()
+ else:
+ self.send_response(http.HTTPStatus.NOT_FOUND)
+ self.end_headers()
+ self.wfile.write("Unknown URL".encode())
+
+ def _send_header(self):
+ self.send_response(http.HTTPStatus.OK)
+ self.send_header("content-type", "application/octet-stream")
+ self.end_headers()
+
+ def _do_registration(self):
+ global stats
+ global last_register
+ clen = int(self.headers.get('content-length'))
+
+ stats.register_calls += 1
+
+ raw_input = self.rfile.read(clen)
+ decoded_doc = bson.BSON.decode(raw_input)
+ last_register = dumps(decoded_doc)
+
+ if not disable_faults and fault_type == FAULT_FAIL_REGISTER:
+ stats.fault_calls += 1
+ self.send_response(http.HTTPStatus.INTERNAL_SERVER_ERROR)
+ self.send_header("content-type", "application/octet-stream")
+ self.end_headers()
+ self.wfile.write("Internal Error of some sort.".encode())
+ return
+
+ if not disable_faults and fault_type == FAULT_INVALID_REGISTER:
+ stats.fault_calls += 1
+ data = bson.BSON.encode({
+ 'version': bson.int64.Int64(42),
+ 'haltMetricsUploading': False,
+ 'id': '',
+ 'informationalURL': 'http://www.example.com/123',
+ 'message': 'Welcome to the Mock Free Monitoring Endpoint',
+ 'reportingInterval': bson.int64.Int64(1),
+ })
+ else:
+ reg_id = 'mock123_' + str(stats.register_calls)
+ if 'id' in decoded_doc:
+ reg_id = decoded_doc['id']
+
+ data = bson.BSON.encode({
+ 'version':
+ bson.int64.Int64(1),
+ 'haltMetricsUploading':
+ False,
+ 'id':
+ reg_id,
+ 'informationalURL':
+ 'http://www.example.com/' + reg_id,
+ 'message':
+ 'Welcome to the Mock Free Monitoring Endpoint',
+ 'reportingInterval':
+ bson.int64.Int64(1),
+ 'userReminder':
+ """To see your monitoring data, navigate to the unique URL below.
+Anyone you share the URL with will also be able to view this page.
+
+https://localhost:8080/someUUID6v5jLKTIZZklDvN5L8sZ
+
+You can disable monitoring at any time by running db.disableFreeMonitoring().""",
+ })
+
+ self._send_header()
+
+ self.wfile.write(data)
+
+ def _do_metrics(self):
+ global stats
+ global last_metrics
+ clen = int(self.headers.get('content-length'))
+
+ stats.metrics_calls += 1
+
+ raw_input = self.rfile.read(clen)
+ decoded_doc = bson.BSON.decode(raw_input)
+ last_metrics = dumps(decoded_doc)
+
+ if not disable_faults and \
+ stats.metrics_calls > 5 and \
+ fault_type == FAULT_HALT_METRICS_5:
+ stats.fault_calls += 1
+ data = bson.BSON.encode({
+ 'version': bson.int64.Int64(1),
+ 'haltMetricsUploading': True,
+ 'permanentlyDelete': False,
+ 'id': 'mock123',
+ 'reportingInterval': bson.int64.Int64(1),
+ 'message': 'Thanks for all the metrics',
+ })
+ elif not disable_faults and \
+ stats.metrics_calls > 3 and fault_type == FAULT_PERMANENTLY_DELETE_AFTER_3:
+ stats.fault_calls += 1
+ data = bson.BSON.encode({
+ 'version': bson.int64.Int64(1),
+ 'haltMetricsUploading': False,
+ 'permanentlyDelete': True,
+ 'id': 'mock123',
+ 'reportingInterval': bson.int64.Int64(1),
+ 'message': 'Thanks for all the metrics',
+ })
+ elif not disable_faults and \
+ stats.metrics_calls > 3 and \
+ stats.fault_calls < 1 and fault_type == FAULT_RESEND_REGISTRATION_ONCE:
+ stats.fault_calls += 1
+ data = bson.BSON.encode({
+ 'version': bson.int64.Int64(2),
+ 'haltMetricsUploading': False,
+ 'permanentlyDelete': False,
+ 'id': 'mock123',
+ 'reportingInterval': bson.int64.Int64(1),
+ 'message': 'Thanks for all the metrics',
+ 'resendRegistration': True,
+ })
+ elif not disable_faults and \
+ stats.metrics_calls == 3 and fault_type == FAULT_RESEND_REGISTRATION_AT_3:
+ stats.fault_calls += 1
+ data = bson.BSON.encode({
+ 'version': bson.int64.Int64(2),
+ 'haltMetricsUploading': False,
+ 'permanentlyDelete': False,
+ 'id': 'mock123',
+ 'reportingInterval': bson.int64.Int64(1),
+ 'message': 'Thanks for all the metrics',
+ 'resendRegistration': True,
+ })
+ else:
+ data = bson.BSON.encode({
+ 'version': bson.int64.Int64(1),
+ 'haltMetricsUploading': False,
+ 'permanentlyDelete': False,
+ 'id': decoded_doc['id'],
+ 'reportingInterval': bson.int64.Int64(1),
+ 'message': 'Thanks for all the metrics',
+ })
+
+ # TODO: test what if header is sent first?
+ self._send_header()
+
+ self.wfile.write(data)
+
+ def _do_stats(self):
+ self._send_header()
+
+ self.wfile.write(str(stats).encode('utf-8'))
+
+ def _do_last_register(self):
+ self._send_header()
+
+ self.wfile.write(str(last_register).encode('utf-8'))
+
+ def _do_last_metrics(self):
+ self._send_header()
+
+ self.wfile.write(str(last_metrics).encode('utf-8'))
+
+ def _do_disable_faults(self):
+ global disable_faults
+ disable_faults = True
+ self._send_header()
+
+ def _do_enable_faults(self):
+ global disable_faults
+ disable_faults = False
+ self._send_header()
+
+
+def run(port, server_class=http.server.HTTPServer, handler_class=FreeMonHandler):
+ """Run web server."""
+ server_address = ('', port)
+
+ http.server.HTTPServer.protocol_version = "HTTP/1.1"
+
+ httpd = server_class(server_address, handler_class)
+
+ print("Mock Web Server Listening on %s" % (str(server_address)))
+
+ httpd.serve_forever()
+
+
+def main():
+ """Main Method."""
+ global fault_type
+ global disable_faults
+
+ parser = argparse.ArgumentParser(description='MongoDB Mock Free Monitoring Endpoint.')
+
+ parser.add_argument('-p', '--port', type=int, default=8000, help="Port to listen on")
+
+ parser.add_argument('-v', '--verbose', action='count', help="Enable verbose tracing")
+
+ parser.add_argument('--fault', type=str, help="Type of fault to inject")
+
+ parser.add_argument('--disable-faults', action='store_true', help="Disable faults on startup")
+
+ args = parser.parse_args()
+ if args.verbose:
+ logging.basicConfig(level=logging.DEBUG)
+
+ if args.fault:
+ if args.fault not in SUPPORTED_FAULT_TYPES:
+ print("Unsupported fault type %s, supports types are %s" % (args.fault,
+ SUPPORTED_FAULT_TYPES))
+ sys.exit(1)
+
+ fault_type = args.fault
+
+ if args.disable_faults:
+ disable_faults = True
+
+ run(args.port)
+
+
+if __name__ == '__main__':
+
+ main()