diff options
-rw-r--r-- | jstests/auth/lib/commands_lib.js | 5 | ||||
-rw-r--r-- | jstests/core/diagdata.js | 26 | ||||
-rw-r--r-- | jstests/libs/ftdc.js | 102 | ||||
-rw-r--r-- | jstests/noPassthrough/ftdc_setdirectory.js | 118 | ||||
-rw-r--r-- | jstests/noPassthroughWithMongod/ftdc_params.js | 54 | ||||
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/base/error_codes.err | 2 | ||||
-rw-r--r-- | src/mongo/db/ftdc/SConscript | 10 | ||||
-rw-r--r-- | src/mongo/db/ftdc/controller.cpp | 31 | ||||
-rw-r--r-- | src/mongo/db/ftdc/controller.h | 15 | ||||
-rw-r--r-- | src/mongo/db/ftdc/controller_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/ftdc/ftdc_mongod.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/ftdc/ftdc_mongos.cpp | 153 | ||||
-rw-r--r-- | src/mongo/db/ftdc/ftdc_mongos.h | 43 | ||||
-rw-r--r-- | src/mongo/db/ftdc/ftdc_server.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/ftdc/ftdc_server.h | 20 | ||||
-rw-r--r-- | src/mongo/db/ftdc/util.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/ftdc/util.h | 5 | ||||
-rw-r--r-- | src/mongo/db/ftdc/util_test.cpp | 21 | ||||
-rw-r--r-- | src/mongo/s/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_ftdc_commands.cpp | 28 | ||||
-rw-r--r-- | src/mongo/s/server.cpp | 6 |
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(); |