summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/lib/commands_lib.js5
-rw-r--r--jstests/core/diagdata.js26
-rw-r--r--jstests/libs/ftdc.js102
-rw-r--r--jstests/noPassthrough/ftdc_setdirectory.js118
-rw-r--r--jstests/noPassthroughWithMongod/ftdc_params.js54
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/base/error_codes.err2
-rw-r--r--src/mongo/db/ftdc/SConscript10
-rw-r--r--src/mongo/db/ftdc/controller.cpp31
-rw-r--r--src/mongo/db/ftdc/controller.h15
-rw-r--r--src/mongo/db/ftdc/controller_test.cpp2
-rw-r--r--src/mongo/db/ftdc/ftdc_mongod.cpp2
-rw-r--r--src/mongo/db/ftdc/ftdc_mongos.cpp153
-rw-r--r--src/mongo/db/ftdc/ftdc_mongos.h43
-rw-r--r--src/mongo/db/ftdc/ftdc_server.cpp9
-rw-r--r--src/mongo/db/ftdc/ftdc_server.h20
-rw-r--r--src/mongo/db/ftdc/util.cpp13
-rw-r--r--src/mongo/db/ftdc/util.h5
-rw-r--r--src/mongo/db/ftdc/util_test.cpp21
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_ftdc_commands.cpp28
-rw-r--r--src/mongo/s/server.cpp6
22 files changed, 580 insertions, 87 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 701e8080f1d..d1e677af08b 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -2756,7 +2756,6 @@ var authCommandsLib = {
{
testname: "getDiagnosticData",
command: {getDiagnosticData: 1},
- skipSharded: true,
testcases: [
{
runOnDb: adminDbName,
@@ -2765,6 +2764,10 @@ var authCommandsLib = {
{resource: {cluster: true}, actions: ["serverStatus"]},
{resource: {cluster: true}, actions: ["replSetGetStatus"]},
{resource: {db: "local", collection: "oplog.rs"}, actions: ["collStats"]},
+ {
+ resource: {cluster: true},
+ actions: ["connPoolStats"]
+ }, // Only needed against mongos
]
},
{runOnDb: firstDbName, roles: {}},
diff --git a/jstests/core/diagdata.js b/jstests/core/diagdata.js
index 490e4a3eb2b..6938f8c5102 100644
--- a/jstests/core/diagdata.js
+++ b/jstests/core/diagdata.js
@@ -1,4 +1,5 @@
// Test that verifies getDiagnosticData returns FTDC data
+load('jstests/libs/ftdc.js');
(function() {
"use strict";
@@ -6,28 +7,5 @@
// Verify we require admin database
assert.commandFailed(db.diagdata.runCommand("getDiagnosticData"));
- // We need to retry a few times if run this test immediately after mongod is started as FTDC may
- // not have run yet.
- var foundGoodDocument = false;
-
- for (var i = 0; i < 60; ++i) {
- var result = db.adminCommand("getDiagnosticData");
- assert.commandWorked(result);
-
- var data = result.data;
-
- if (!data.hasOwnProperty("start")) {
- // Wait a little longer for FTDC to start
- sleep(500);
- } else {
- // Check for a few common properties to ensure we got data
- assert(data.hasOwnProperty("serverStatus"),
- "does not have 'serverStatus' in '" + tojson(data) + "'");
- assert(data.hasOwnProperty("end"), "does not have 'end' in '" + tojson(data) + "'");
- foundGoodDocument = true;
- }
- }
- assert(foundGoodDocument,
- "getDiagnosticData failed to return a non-empty command, is FTDC running?");
-
+ verifyGetDiagnosticData(db.getSiblingDB('admin'));
})();
diff --git a/jstests/libs/ftdc.js b/jstests/libs/ftdc.js
new file mode 100644
index 00000000000..7d327d39852
--- /dev/null
+++ b/jstests/libs/ftdc.js
@@ -0,0 +1,102 @@
+/**
+ * Utility test functions for FTDC
+ */
+'use strict';
+
+/**
+ * Verify that getDiagnosticData is working correctly.
+ */
+function verifyGetDiagnosticData(adminDb) {
+ // We need to retry a few times if run this test immediately after mongod is started as FTDC may
+ // not have run yet.
+ var foundGoodDocument = false;
+
+ for (var i = 0; i < 60 && foundGoodDocument == false; ++i) {
+ var result = adminDb.runCommand("getDiagnosticData");
+ assert.commandWorked(result);
+
+ var data = result.data;
+
+ if (!data.hasOwnProperty("start")) {
+ // Wait a little longer for FTDC to start
+ jsTestLog("Running getDiagnosticData: " + tojson(result));
+
+ sleep(500);
+ } else {
+ // Check for a few common properties to ensure we got data
+ assert(data.hasOwnProperty("serverStatus"),
+ "does not have 'serverStatus' in '" + tojson(data) + "'");
+ assert(data.hasOwnProperty("end"), "does not have 'end' in '" + tojson(data) + "'");
+ foundGoodDocument = true;
+
+ jsTestLog("Got good getDiagnosticData: " + tojson(result));
+ }
+ }
+
+ assert(foundGoodDocument,
+ "getDiagnosticData failed to return a non-empty command, is FTDC running?");
+}
+
+/**
+ * Validate all the common FTDC parameters are set correctly and can be manipulated.
+ */
+function verifyCommonFTDCParameters(adminDb, isEnabled) {
+ // Are we running against MongoS?
+ var isMongos = ("isdbgrid" == adminDb.runCommand("ismaster").msg);
+
+ // Check the defaults are correct
+ //
+ function getparam(field) {
+ var q = {getParameter: 1};
+ q[field] = 1;
+
+ var ret = adminDb.runCommand(q);
+ return ret[field];
+ }
+
+ // Verify the defaults are as we documented them
+ assert.eq(getparam("diagnosticDataCollectionEnabled"), isEnabled);
+ assert.eq(getparam("diagnosticDataCollectionPeriodMillis"), 1000);
+ assert.eq(getparam("diagnosticDataCollectionDirectorySizeMB"), 200);
+ assert.eq(getparam("diagnosticDataCollectionFileSizeMB"), 10);
+ assert.eq(getparam("diagnosticDataCollectionSamplesPerChunk"), 300);
+ assert.eq(getparam("diagnosticDataCollectionSamplesPerInterimUpdate"), 10);
+
+ function setparam(obj) {
+ var ret = adminDb.runCommand(Object.extend({setParameter: 1}, obj));
+ return ret;
+ }
+
+ if (!isMongos) {
+ // The MongoS specific behavior for diagnosticDataCollectionEnabled is tested in
+ // ftdc_setdirectory.js.
+ assert.commandWorked(setparam({"diagnosticDataCollectionEnabled": 1}));
+ }
+ assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 100}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 1}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 2}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 2}));
+
+ // Negative tests - set values below minimums
+ assert.commandFailed(setparam({"diagnosticDataCollectionPeriodMillis": 1}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 1}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerChunk": 1}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 1}));
+
+ // Negative test - set file size bigger then directory size
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionFileSizeMB": 100}));
+
+ // Negative test - set directory size less then file size
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 100}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 50}));
+ assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
+
+ // Reset
+ assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 10}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 200}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 1000}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 300}));
+ assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 10}));
+} \ No newline at end of file
diff --git a/jstests/noPassthrough/ftdc_setdirectory.js b/jstests/noPassthrough/ftdc_setdirectory.js
new file mode 100644
index 00000000000..c1173055e2a
--- /dev/null
+++ b/jstests/noPassthrough/ftdc_setdirectory.js
@@ -0,0 +1,118 @@
+/**
+ * Test that verifies FTDC works in mongos.
+ */
+load('jstests/libs/ftdc.js');
+
+(function() {
+ 'use strict';
+ let testPath1 = MongoRunner.toRealPath('ftdc_setdir1');
+ let testPath2 = MongoRunner.toRealPath('ftdc_setdir2');
+ let testPath3 = MongoRunner.toRealPath('ftdc_setdir3');
+ let testLog3 = testPath3 + "mongos_ftdc.log";
+
+ // Make the log file directory for mongos.
+ mkdir(testPath3);
+
+ // Startup 3 mongos:
+ // 1. Normal MongoS with no log file to verify FTDC can be startup at runtime with a path.
+ // 2. MongoS with explict diagnosticDataCollectionDirectoryPath setParameter at startup.
+ // 3. MongoS with log file to verify automatic FTDC path computation works.
+ let st = new ShardingTest({
+ shards: 1,
+ mongos: {
+ s0: {verbose: 0},
+ s1: {setParameter: {diagnosticDataCollectionDirectoryPath: testPath2}},
+ s2: {logpath: testLog3}
+ }
+ });
+
+ let admin1 = st.s0.getDB('admin');
+ let admin2 = st.s1.getDB('admin');
+ let admin3 = st.s2.getDB('admin');
+
+ function setParam(admin, obj) {
+ var ret = admin.runCommand(Object.extend({setParameter: 1}, obj));
+ return ret;
+ }
+
+ function getParam(admin, field) {
+ var q = {getParameter: 1};
+ q[field] = 1;
+
+ var ret = admin.runCommand(q);
+ assert.commandWorked(ret);
+ return ret[field];
+ }
+
+ // Verify FTDC can be started at runtime.
+ function verifyFTDCDisabledOnStartup() {
+ jsTestLog("Running verifyFTDCDisabledOnStartup");
+ verifyCommonFTDCParameters(admin1, false);
+
+ // 1. Try to enable and fail
+ assert.commandFailed(setParam(admin1, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 2. Set path and succeed
+ assert.commandWorked(
+ setParam(admin1, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 3. Set path again and fail
+ assert.commandFailed(
+ setParam(admin1, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 4. Enable successfully
+ assert.commandWorked(setParam(admin1, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 5. Validate getDiagnosticData returns FTDC data now
+ jsTestLog("Verifying FTDC getDiagnosticData");
+ verifyGetDiagnosticData(admin1);
+ }
+
+ // Verify FTDC is already running if there was a path set at startup.
+ function verifyFTDCStartsWithPath() {
+ jsTestLog("Running verifyFTDCStartsWithPath");
+ verifyCommonFTDCParameters(admin2, true);
+
+ // 1. Set path fail
+ assert.commandFailed(
+ setParam(admin2, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 2. Enable successfully
+ assert.commandWorked(setParam(admin2, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 3. Validate getDiagnosticData returns FTDC data now
+ jsTestLog("Verifying FTDC getDiagnosticData");
+ verifyGetDiagnosticData(admin2);
+ }
+
+ function normpath(path) {
+ return path.replace(/\\/g, "/");
+ }
+
+ // Verify FTDC is already running if there was a path set at startup.
+ function verifyFTDCStartsWithLogFile() {
+ jsTestLog("Running verifyFTDCStartsWithLogFile");
+ verifyCommonFTDCParameters(admin3, true);
+
+ // 1. Verify that path is computed correctly.
+ let computedPath = getParam(admin3, "diagnosticDataCollectionDirectoryPath");
+ assert.eq(normpath(computedPath), normpath(testPath3 + "mongos_ftdc.diagnostic.data"));
+
+ // 2. Set path fail
+ assert.commandFailed(
+ setParam(admin3, {"diagnosticDataCollectionDirectoryPath": testPath2}));
+
+ // 3. Enable successfully
+ assert.commandWorked(setParam(admin3, {"diagnosticDataCollectionEnabled": 1}));
+
+ // 4. Validate getDiagnosticData returns FTDC data now
+ jsTestLog("Verifying FTDC getDiagnosticData");
+ verifyGetDiagnosticData(admin3);
+ }
+
+ verifyFTDCDisabledOnStartup();
+ verifyFTDCStartsWithPath();
+ verifyFTDCStartsWithLogFile();
+
+ st.stop();
+})();
diff --git a/jstests/noPassthroughWithMongod/ftdc_params.js b/jstests/noPassthroughWithMongod/ftdc_params.js
index ced6c1b5675..08714040fcb 100644
--- a/jstests/noPassthroughWithMongod/ftdc_params.js
+++ b/jstests/noPassthroughWithMongod/ftdc_params.js
@@ -1,58 +1,10 @@
// FTDC test cases
//
+load('jstests/libs/ftdc.js');
+
(function() {
'use strict';
var admin = db.getSiblingDB("admin");
- // Check the defaults are correct
- //
- function getparam(field) {
- var q = {getParameter: 1};
- q[field] = 1;
-
- var ret = admin.runCommand(q);
- return ret[field];
- }
-
- // Verify the defaults are as we documented them
- assert.eq(getparam("diagnosticDataCollectionEnabled"), true);
- assert.eq(getparam("diagnosticDataCollectionPeriodMillis"), 1000);
- assert.eq(getparam("diagnosticDataCollectionDirectorySizeMB"), 200);
- assert.eq(getparam("diagnosticDataCollectionFileSizeMB"), 10);
- assert.eq(getparam("diagnosticDataCollectionSamplesPerChunk"), 300);
- assert.eq(getparam("diagnosticDataCollectionSamplesPerInterimUpdate"), 10);
-
- function setparam(obj) {
- var ret = admin.runCommand(Object.extend({setParameter: 1}, obj));
- return ret;
- }
-
- assert.commandWorked(setparam({"diagnosticDataCollectionEnabled": 1}));
- assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 100}));
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
- assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 1}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 2}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 2}));
-
- // Negative tests - set values below minimums
- assert.commandFailed(setparam({"diagnosticDataCollectionPeriodMillis": 1}));
- assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 1}));
- assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerChunk": 1}));
- assert.commandFailed(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 1}));
-
- // Negative test - set file size bigger then directory size
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
- assert.commandFailed(setparam({"diagnosticDataCollectionFileSizeMB": 100}));
-
- // Negative test - set directory size less then file size
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 100}));
- assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 50}));
- assert.commandFailed(setparam({"diagnosticDataCollectionDirectorySizeMB": 10}));
-
- // Reset
- assert.commandWorked(setparam({"diagnosticDataCollectionFileSizeMB": 10}));
- assert.commandWorked(setparam({"diagnosticDataCollectionDirectorySizeMB": 200}));
- assert.commandWorked(setparam({"diagnosticDataCollectionPeriodMillis": 1000}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerChunk": 300}));
- assert.commandWorked(setparam({"diagnosticDataCollectionSamplesPerInterimUpdate": 10}));
+ verifyCommonFTDCParameters(admin, true);
})();
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index ec13d183c1b..1dad974302b 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -363,6 +363,7 @@ env.Install(
'db/commands/core',
'db/commands/server_status',
'db/conn_pool_options',
+ 'db/ftdc/ftdc_mongos',
'db/logical_time_metadata_hook',
'db/mongodandmongos',
'db/server_options',
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index c9a6a97c593..fa980506671 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -216,6 +216,8 @@ error_code("DatabaseDropPending", 215)
error_code("ElectionInProgress", 216)
error_code("IncompleteTransactionHistory", 217);
error_code("UpdateOperationFailed", 218)
+error_code("FTDCPathNotSet", 219)
+error_code("FTDCPathAlreadySet", 220)
# Error codes 4000-8999 are reserved.
diff --git a/src/mongo/db/ftdc/SConscript b/src/mongo/db/ftdc/SConscript
index 222f5b1e112..5e78036c3e5 100644
--- a/src/mongo/db/ftdc/SConscript
+++ b/src/mongo/db/ftdc/SConscript
@@ -70,6 +70,16 @@ env.Library(
],
)
+env.Library(
+ target='ftdc_mongos',
+ source=[
+ 'ftdc_mongos.cpp',
+ ],
+ LIBDEPS_PRIVATE=[
+ 'ftdc_server'
+ ],
+)
+
env.CppUnitTest(
target='ftdc_test',
source=[
diff --git a/src/mongo/db/ftdc/controller.cpp b/src/mongo/db/ftdc/controller.cpp
index 9e2987db0f9..bc3d4df444c 100644
--- a/src/mongo/db/ftdc/controller.cpp
+++ b/src/mongo/db/ftdc/controller.cpp
@@ -47,9 +47,19 @@
namespace mongo {
-void FTDCController::setEnabled(bool enabled) {
+Status FTDCController::setEnabled(bool enabled) {
stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ if (_path.empty()) {
+ return Status(ErrorCodes::FTDCPathNotSet,
+ str::stream() << "FTDC cannot be enabled without setting the set parameter "
+ "'diagnosticDataCollectionDirectoryPath' first.");
+ }
+
_configTemp.enabled = enabled;
+ _condvar.notify_one();
+
+ return Status::OK();
}
void FTDCController::setPeriod(Milliseconds millis) {
@@ -82,6 +92,23 @@ void FTDCController::setMaxSamplesPerInterimMetricChunk(size_t size) {
_condvar.notify_one();
}
+Status FTDCController::setDirectory(const boost::filesystem::path& path) {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ if (!_path.empty()) {
+ return Status(ErrorCodes::FTDCPathAlreadySet,
+ str::stream() << "FTDC path has already been set to '" << _path.string()
+ << "'. It cannot be changed.");
+ }
+
+ _path = path;
+
+ // Do not notify for the change since it has to be enabled via setEnabled.
+
+ return Status::OK();
+}
+
+
void FTDCController::addPeriodicCollector(std::unique_ptr<FTDCCollectorInterface> collector) {
{
stdx::lock_guard<stdx::mutex> lock(_mutex);
@@ -203,7 +230,7 @@ void FTDCController::doLoop() {
}
// TODO: consider only running this thread if we are enabled
- // for now, we just keep an idle thread as it is simplier
+ // for now, we just keep an idle thread as it is simpler
if (_config.enabled) {
// Delay initialization of FTDCFileManager until we are sure the user has enabled
// FTDC
diff --git a/src/mongo/db/ftdc/controller.h b/src/mongo/db/ftdc/controller.h
index 44b5e838506..e73d570598d 100644
--- a/src/mongo/db/ftdc/controller.h
+++ b/src/mongo/db/ftdc/controller.h
@@ -62,8 +62,12 @@ public:
/*
* Set whether the controller is enabled, and collects data.
+ *
+ * Returns ErrorCodes::FTDCPathNotSet if no log path has been specified for FTDC. This occurs
+ * in MongoS in some situations since MongoS is not required to have a storage directory like
+ * MongoD does.
*/
- void setEnabled(bool enabled);
+ Status setEnabled(bool enabled);
/**
* Set the period for data collection.
@@ -94,6 +98,13 @@ public:
*/
void setMaxSamplesPerInterimMetricChunk(size_t size);
+ /*
+ * Set the path to store FTDC files if not already set.
+ *
+ * Returns ErrorCodes::FTDCPathAlreadySet if the path has already been set.
+ */
+ Status setDirectory(const boost::filesystem::path& path);
+
/**
* Add a metric collector to collect periodically. i.e., serverStatus
*/
@@ -172,7 +183,7 @@ private:
State _state{State::kNotStarted};
// Directory to store files
- const boost::filesystem::path _path;
+ boost::filesystem::path _path;
// Mutex to protect the condvar, configuration changes, and most recent periodic document.
stdx::mutex _mutex;
diff --git a/src/mongo/db/ftdc/controller_test.cpp b/src/mongo/db/ftdc/controller_test.cpp
index ba545b3ad04..c307d02cca8 100644
--- a/src/mongo/db/ftdc/controller_test.cpp
+++ b/src/mongo/db/ftdc/controller_test.cpp
@@ -254,7 +254,7 @@ TEST(FTDCControllerTest, TestStartAsDisabled) {
ASSERT_EQUALS(files0.size(), 0UL);
- c.setEnabled(true);
+ ASSERT_OK(c.setEnabled(true));
c1Ptr->setSignalOnCount(50);
diff --git a/src/mongo/db/ftdc/ftdc_mongod.cpp b/src/mongo/db/ftdc/ftdc_mongod.cpp
index 32f5e4b27f6..1f53ec345ea 100644
--- a/src/mongo/db/ftdc/ftdc_mongod.cpp
+++ b/src/mongo/db/ftdc/ftdc_mongod.cpp
@@ -66,7 +66,7 @@ void startMongoDFTDC() {
boost::filesystem::path dir(storageGlobalParams.dbpath);
dir /= kFTDCDefaultDirectory.toString();
- startFTDC(dir, registerMongoDCollectors);
+ startFTDC(dir, FTDCStartMode::kStart, registerMongoDCollectors);
}
void stopMongoDFTDC() {
diff --git a/src/mongo/db/ftdc/ftdc_mongos.cpp b/src/mongo/db/ftdc/ftdc_mongos.cpp
new file mode 100644
index 00000000000..bdacaa7b6e1
--- /dev/null
+++ b/src/mongo/db/ftdc/ftdc_mongos.cpp
@@ -0,0 +1,153 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kFTDC
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/ftdc/ftdc_mongos.h"
+
+#include <boost/filesystem.hpp>
+
+#include "mongo/db/ftdc/controller.h"
+#include "mongo/db/ftdc/ftdc_server.h"
+#include "mongo/db/server_parameters.h"
+#include "mongo/stdx/thread.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+namespace {
+
+/**
+ * Expose diagnosticDataCollectionDirectoryPath set parameter to specify the MongoS FTDC path.
+ */
+class ExportedFTDCDirectoryPathParameter : public ServerParameter {
+public:
+ ExportedFTDCDirectoryPathParameter()
+ : ServerParameter(ServerParameterSet::getGlobal(),
+ "diagnosticDataCollectionDirectoryPath",
+ true,
+ true) {}
+
+
+ void append(OperationContext* opCtx, BSONObjBuilder& b, const std::string& name) final {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+ b.append(name, _path.generic_string());
+ }
+
+ Status set(const BSONElement& newValueElement) {
+ if (newValueElement.type() != String) {
+ return Status(ErrorCodes::BadValue,
+ "diagnosticDataCollectionDirectoryPath only supports type string");
+ }
+
+ std::string str = newValueElement.str();
+ return setFromString(str);
+ }
+
+ Status setFromString(const std::string& str) final {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+
+ FTDCController* controller = nullptr;
+
+ if (hasGlobalServiceContext()) {
+ controller = FTDCController::get(getGlobalServiceContext());
+ }
+
+ if (controller) {
+ Status s = controller->setDirectory(str);
+ if (!s.isOK()) {
+ return s;
+ }
+ }
+
+ _path = str;
+
+ return Status::OK();
+ }
+
+ boost::filesystem::path getDirectory() {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+ return _path;
+ }
+
+ void setDirectory(boost::filesystem::path& path) {
+ stdx::lock_guard<stdx::mutex> guard(_lock);
+ _path = path;
+ }
+
+private:
+ // Lock to guard _path
+ stdx::mutex _lock;
+
+ // Directory location of ftdc files, guarded by _lock
+ boost::filesystem::path _path;
+} exportedFTDCDirectoryPathParameter;
+
+void registerMongoSCollectors(FTDCController* controller) {
+ // PoolStats
+ controller->addPeriodicCollector(stdx::make_unique<FTDCSimpleInternalCommandCollector>(
+ "connPoolStats", "connPoolStats", "", BSON("connPoolStats" << 1)));
+}
+
+} // namespace
+
+void startMongoSFTDC() {
+ // Get the path to use for FTDC:
+ // 1. Check if the user set one.
+ // 2. If not, check if the user has a logpath and derive one.
+ // 3. Otherwise, tell the user FTDC cannot run.
+
+ // Only attempt to enable FTDC if we have a path to log files to.
+ FTDCStartMode startMode = FTDCStartMode::kStart;
+ auto directory = exportedFTDCDirectoryPathParameter.getDirectory();
+
+ if (directory.empty()) {
+ if (serverGlobalParams.logpath.empty()) {
+ warning() << "FTDC is disabled because neither '--logpath' nor set parameter "
+ "'diagnosticDataCollectionDirectoryPath' are specified.";
+ startMode = FTDCStartMode::kSkipStart;
+ } else {
+ directory = FTDCUtil::getMongoSPath(serverGlobalParams.logpath);
+
+ // Update the server parameter with the computed path.
+ // Note: If the computed FTDC directory conflicts with an existing file, then FTDC will
+ // warn about the conflict, and not startup. It will not terminate MongoS in this
+ // situation.
+ exportedFTDCDirectoryPathParameter.setDirectory(directory);
+ }
+ }
+
+ startFTDC(directory, startMode, registerMongoSCollectors);
+}
+
+void stopMongoSFTDC() {
+ stopFTDC();
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_mongos.h b/src/mongo/db/ftdc/ftdc_mongos.h
new file mode 100644
index 00000000000..4d8ff8fc08b
--- /dev/null
+++ b/src/mongo/db/ftdc/ftdc_mongos.h
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+namespace mongo {
+
+/**
+ * Start Full Time Data Capture
+ */
+void startMongoSFTDC();
+
+/**
+ * Stop Full Time Data Capture
+ */
+void stopMongoSFTDC();
+
+} // namespace mongo
diff --git a/src/mongo/db/ftdc/ftdc_server.cpp b/src/mongo/db/ftdc/ftdc_server.cpp
index 8bcc0426e73..534cbf37650 100644
--- a/src/mongo/db/ftdc/ftdc_server.cpp
+++ b/src/mongo/db/ftdc/ftdc_server.cpp
@@ -74,7 +74,7 @@ public:
virtual Status validate(const bool& potentialNewValue) {
auto controller = getGlobalFTDCController();
if (controller) {
- controller->setEnabled(potentialNewValue);
+ return controller->setEnabled(potentialNewValue);
}
return Status::OK();
@@ -272,9 +272,14 @@ std::string FTDCSimpleInternalCommandCollector::name() const {
// Note: This must be run before the server parameters are parsed during startup
// so that the FTDCController is initialized.
//
-void startFTDC(boost::filesystem::path& path, RegisterCollectorsFunction registerCollectors) {
+void startFTDC(boost::filesystem::path& path,
+ FTDCStartMode startupMode,
+ RegisterCollectorsFunction registerCollectors) {
FTDCConfig config;
config.period = Milliseconds(localPeriodMillis.load());
+ // Only enable FTDC if our caller says to enable FTDC, MongoS may not have a valid path to write
+ // files to so update the diagnosticDataCollectionEnabled set parameter to reflect that.
+ localEnabledFlag.store(startupMode == FTDCStartMode::kStart && localEnabledFlag.load());
config.enabled = localEnabledFlag.load();
config.maxFileSizeBytes = localMaxFileSizeMB.load() * 1024 * 1024;
config.maxDirectorySizeBytes = localMaxDirectorySizeMB.load() * 1024 * 1024;
diff --git a/src/mongo/db/ftdc/ftdc_server.h b/src/mongo/db/ftdc/ftdc_server.h
index b9f238801cd..4b4583ae153 100644
--- a/src/mongo/db/ftdc/ftdc_server.h
+++ b/src/mongo/db/ftdc/ftdc_server.h
@@ -46,12 +46,30 @@ namespace mongo {
using RegisterCollectorsFunction = stdx::function<void(FTDCController*)>;
/**
+ * An enum that decides whether FTDC will startup as part of startup or if its deferred to later.
+ */
+enum class FTDCStartMode {
+
+ /**
+ * Skip starting FTDC since it missing a file storage location.
+ */
+ kSkipStart,
+
+ /**
+ * Start FTDC because it has a path to store files.
+ */
+ kStart,
+};
+
+/**
* Start Full Time Data Capture
* Starts 1 thread.
*
* See MongoD and MongoS specific functions.
*/
-void startFTDC(boost::filesystem::path& path, RegisterCollectorsFunction registerCollectors);
+void startFTDC(boost::filesystem::path& path,
+ FTDCStartMode startupMode,
+ RegisterCollectorsFunction registerCollectors);
/**
* Stop Full Time Data Capture
diff --git a/src/mongo/db/ftdc/util.cpp b/src/mongo/db/ftdc/util.cpp
index d56eb8ca380..243ba90b666 100644
--- a/src/mongo/db/ftdc/util.cpp
+++ b/src/mongo/db/ftdc/util.cpp
@@ -103,6 +103,19 @@ Date_t roundTime(Date_t now, Milliseconds period) {
return Date_t::fromMillisSinceEpoch(next_time);
}
+boost::filesystem::path getMongoSPath(const boost::filesystem::path& logFile) {
+ auto base = logFile;
+
+ // Keep stripping file extensions until we are only left with the file name
+ while (base.has_extension()) {
+ auto full_path = base.generic_string();
+ base = full_path.substr(0, full_path.size() - base.extension().size());
+ }
+
+ base += "." + kFTDCDefaultDirectory.toString();
+ return base;
+}
+
} // namespace FTDCUtil
diff --git a/src/mongo/db/ftdc/util.h b/src/mongo/db/ftdc/util.h
index 0cb4d19ce7f..4816c534f96 100644
--- a/src/mongo/db/ftdc/util.h
+++ b/src/mongo/db/ftdc/util.h
@@ -190,6 +190,11 @@ boost::filesystem::path getInterimTempFile(const boost::filesystem::path& file);
*/
Date_t roundTime(Date_t now, Milliseconds period);
+/**
+ * Get the storage path for MongoS from the log file path.
+ */
+boost::filesystem::path getMongoSPath(const boost::filesystem::path& logFile);
+
} // namespace FTDCUtil
} // namespace mongo
diff --git a/src/mongo/db/ftdc/util_test.cpp b/src/mongo/db/ftdc/util_test.cpp
index aa99006c73a..0adf0f73c15 100644
--- a/src/mongo/db/ftdc/util_test.cpp
+++ b/src/mongo/db/ftdc/util_test.cpp
@@ -46,4 +46,25 @@ TEST(FTDCUtilTest, TestRoundTime) {
checkTime(14, 13, 7);
}
+// Validate the MongoS FTDC path is computed correctly from a log file path.
+TEST(FTDCUtilTest, TestMongoSPath) {
+
+ std::vector<std::pair<std::string, std::string>> testCases = {
+ {"/var/log/mongos.log", "/var/log/mongos.diagnostic.data"},
+ {"/var/log/mongos.foo.log", "/var/log/mongos.diagnostic.data"},
+ {"/var/log/log_file", "/var/log/log_file.diagnostic.data"},
+ {"./mongos.log", "./mongos.diagnostic.data"},
+ {"../mongos.log", "../mongos.diagnostic.data"},
+ {"c:\\var\\log\\mongos.log", "c:\\var\\log\\mongos.diagnostic.data"},
+ {"c:\\var\\log\\mongos.foo.log", "c:\\var\\log\\mongos.diagnostic.data"},
+ {"c:\\var\\log\\log_file", "c:\\var\\log\\log_file.diagnostic.data"},
+ {"/var/some.log/mongos.log", "/var/some.log/mongos.diagnostic.data"},
+ {"/var/some.log/log_file", "/var/some.log/log_file.diagnostic.data"},
+ };
+
+ for (const auto& p : testCases) {
+ ASSERT_EQUALS(FTDCUtil::getMongoSPath(p.first), p.second);
+ }
+}
+
} // namespace mongo
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 122d7e7f5b5..58588b5a7b7 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -86,6 +86,7 @@ env.Library(
'$BUILD_DIR/mongo/db/auth/authmongos',
'$BUILD_DIR/mongo/db/commands/apply_ops_cmd_common',
'$BUILD_DIR/mongo/db/commands/killcursors_common',
+ '$BUILD_DIR/mongo/db/ftdc/ftdc_server',
'$BUILD_DIR/mongo/db/pipeline/aggregation',
'$BUILD_DIR/mongo/db/views/views',
'$BUILD_DIR/mongo/rpc/client_metadata',
diff --git a/src/mongo/s/commands/cluster_ftdc_commands.cpp b/src/mongo/s/commands/cluster_ftdc_commands.cpp
index f097353d1dc..61b35db405e 100644
--- a/src/mongo/s/commands/cluster_ftdc_commands.cpp
+++ b/src/mongo/s/commands/cluster_ftdc_commands.cpp
@@ -67,6 +67,28 @@ public:
Status checkAuthForCommand(Client* client,
const std::string& dbname,
const BSONObj& cmdObj) override {
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::serverStatus)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::replSetGetStatus)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::connPoolStats)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
+ if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forExactNamespace(NamespaceString("local", "oplog.rs")),
+ ActionType::collStats)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
+ }
+
return Status::OK();
}
@@ -76,9 +98,11 @@ public:
std::string& errmsg,
BSONObjBuilder& result) override {
- errmsg = "getDiagnosticData not allowed through mongos";
+ result.append(
+ "data",
+ FTDCController::get(opCtx->getServiceContext())->getMostRecentPeriodicDocument());
- return false;
+ return true;
}
};
diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp
index 187451d237c..67a6ef16c6e 100644
--- a/src/mongo/s/server.cpp
+++ b/src/mongo/s/server.cpp
@@ -50,6 +50,7 @@
#include "mongo/db/auth/authz_manager_external_state_s.h"
#include "mongo/db/auth/user_cache_invalidator_job.h"
#include "mongo/db/client.h"
+#include "mongo/db/ftdc/ftdc_mongos.h"
#include "mongo/db/initialize_server_global_state.h"
#include "mongo/db/lasterror.h"
#include "mongo/db/log_process_details.h"
@@ -174,6 +175,9 @@ static void cleanupTask() {
if (auto catalog = Grid::get(opCtx)->catalogClient(opCtx)) {
catalog->shutDown(opCtx);
}
+
+ // Shutdown Full-Time Data Capture
+ stopMongoSFTDC();
}
audit::logShutdown(Client::getCurrent());
@@ -254,6 +258,8 @@ static ExitCode runMongosServer() {
auto sep = stdx::make_unique<ServiceEntryPointMongos>(getGlobalServiceContext());
getGlobalServiceContext()->setServiceEntryPoint(std::move(sep));
+ startMongoSFTDC();
+
auto tl = transport::TransportLayerManager::createWithConfig(&serverGlobalParams,
getGlobalServiceContext());
auto res = tl->setup();