// grid.cpp
/**
* Copyright (C) 2010 10gen 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 .
*
* 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::kSharding
#include "mongo/platform/basic.h"
#include "mongo/s/grid.h"
#include
#include "mongo/client/connpool.h"
#include "mongo/db/client.h"
#include "mongo/db/json.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/write_concern.h"
#include "mongo/s/catalog/legacy/catalog_manager_legacy.h"
#include "mongo/s/cluster_write.h"
#include "mongo/s/mongos_options.h"
#include "mongo/s/shard.h"
#include "mongo/s/type_collection.h"
#include "mongo/s/type_database.h"
#include "mongo/s/type_settings.h"
#include "mongo/s/type_shard.h"
#include "mongo/util/fail_point_service.h"
#include "mongo/util/log.h"
#include "mongo/util/stringutils.h"
namespace mongo {
using std::endl;
using std::map;
using std::set;
using std::vector;
MONGO_FP_DECLARE(neverBalance);
Grid::Grid() : _allowLocalShard(true) {
}
bool Grid::initCatalogManager(const std::vector& configHosts) {
std::auto_ptr cm(new CatalogManagerLegacy());
Status status = cm->init(configHosts);
if (!status.isOK()) {
severe() << "Catalog manager failed to initialize " << status;
return false;
}
_catalogManager.reset(cm.release());
return true;
}
DBConfigPtr Grid::getDBConfig( StringData ns , bool create , const string& shardNameHint ) {
string database = nsToDatabase( ns );
uassert( 15918,
str::stream() << "invalid database name: " << database,
NamespaceString::validDBName( database ) );
boost::lock_guard l( _lock );
DBConfigPtr& dbConfig = _databases[database];
if( ! dbConfig ){
dbConfig.reset(new DBConfig( database ));
// Protect initial load from connectivity errors, otherwise we won't be able
// to perform any task.
bool loaded = false;
try {
try {
loaded = dbConfig->load();
}
catch ( const DBException& ) {
// Retry again, if the config server are now up, the previous call should have
// cleared all the bad connections in the pool and this should succeed.
loaded = dbConfig->load();
}
}
catch( DBException& e ){
e.addContext( "error loading initial database config information" );
warning() << e.what() << endl;
dbConfig.reset();
throw;
}
if( ! loaded ){
if( create ){
// Protect creation of initial db doc from connectivity errors
try{
// note here that cc->primary == 0.
log() << "couldn't find database [" << database << "] in config db" << endl;
{
// lets check case
ScopedDbConnection conn(configServer.modelServer(), 30);
BSONObjBuilder b;
b.appendRegex( "_id" , (string)"^" +
pcrecpp::RE::QuoteMeta( database ) + "$" , "i" );
BSONObj dbObj = conn->findOne( DatabaseType::ConfigNS , b.obj() );
conn.done();
// If our name is exactly the same as the name we want, try loading
// the database again.
if (!dbObj.isEmpty() &&
dbObj[DatabaseType::name()].String() == database)
{
if (dbConfig->load()) return dbConfig;
}
// TODO: This really shouldn't fall through, but without metadata
// management there's no good way to make sure this works all the time
// when the database is getting rapidly created and dropped.
// For now, just do exactly what we used to do.
if ( ! dbObj.isEmpty() ) {
uasserted( DatabaseDifferCaseCode, str::stream()
<< "can't have 2 databases that just differ on case "
<< " have: " << dbObj[DatabaseType::name()].String()
<< " want to add: " << database );
}
}
Shard primary;
if (database == "admin" || database == "config") {
primary = configServer.getPrimary();
}
else if ( shardNameHint.empty() ) {
primary = Shard::pick();
}
else {
// use the shard name if provided
Shard shard;
shard.reset( shardNameHint );
primary = shard;
}
if ( primary.ok() ) {
dbConfig->setPrimary( primary.getName() ); // saves 'cc' to configDB
log() << "\t put [" << database << "] on: " << primary << endl;
}
else {
uasserted( 10185 , "can't find a shard to put new db on" );
}
}
catch( DBException& e ){
e.addContext( "error creating initial database config information" );
warning() << e.what() << endl;
dbConfig.reset();
throw;
}
}
else {
dbConfig.reset();
}
}
}
return dbConfig;
}
void Grid::removeDB( const std::string& database ) {
uassert( 10186 , "removeDB expects db name" , database.find( '.' ) == string::npos );
boost::lock_guard l( _lock );
_databases.erase( database );
}
void Grid::removeDBIfExists(const DBConfig& database) {
boost::lock_guard l(_lock);
map::iterator it = _databases.find(database.name());
if (it != _databases.end() && it->second.get() == &database) {
_databases.erase(it);
log() << "erased database " << database.name() << " from local registry";
}
else {
log() << database.name() << "already erased from local registry";
}
}
bool Grid::allowLocalHost() const {
return _allowLocalShard;
}
void Grid::setAllowLocalHost( bool allow ) {
_allowLocalShard = allow;
}
bool Grid::knowAboutShard( const string& name ) const {
ScopedDbConnection conn(configServer.getPrimary().getConnString(), 30);
BSONObj shard = conn->findOne(ShardType::ConfigNS, BSON(ShardType::host(name)));
conn.done();
return ! shard.isEmpty();
}
/*
* Returns whether balancing is enabled, with optional namespace "ns" parameter for balancing on a particular
* collection.
*/
bool Grid::shouldBalance(const SettingsType& balancerSettings) const {
// Allow disabling the balancer for testing
if (MONGO_FAIL_POINT(neverBalance)) return false;
if (balancerSettings.isBalancerStoppedSet() && balancerSettings.getBalancerStopped()) {
return false;
}
if (balancerSettings.isBalancerActiveWindowSet()) {
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
return _inBalancingWindow(balancerSettings.getBalancerActiveWindow(), now);
}
return true;
}
bool Grid::getBalancerSettings(SettingsType* settings, string* errMsg) const {
BSONObj balancerDoc;
ScopedDbConnection conn(configServer.getPrimary().getConnString(), 30);
try {
balancerDoc = conn->findOne(SettingsType::ConfigNS,
BSON(SettingsType::key("balancer")));
conn.done();
}
catch (const DBException& ex) {
*errMsg = str::stream() << "failed to read balancer settings from " << conn.getHost()
<< ": " << causedBy(ex);
return false;
}
return settings->parseBSON(balancerDoc, errMsg);
}
bool Grid::getConfigShouldBalance() const {
SettingsType balSettings;
string errMsg;
if (!getBalancerSettings(&balSettings, &errMsg)) {
warning() << errMsg;
return false;
}
if (!balSettings.isKeySet()) {
// Balancer settings doc does not exist. Default to yes.
return true;
}
return shouldBalance(balSettings);
}
bool Grid::getCollShouldBalance(const std::string& ns) const {
BSONObj collDoc;
ScopedDbConnection conn(configServer.getPrimary().getConnString(), 30);
try {
collDoc = conn->findOne(CollectionType::ConfigNS, BSON(CollectionType::ns(ns)));
conn.done();
}
catch (const DBException& e){
conn.kill();
warning() << "could not determine whether balancer should be running, error getting"
<< "config data from " << conn.getHost() << causedBy(e) << endl;
// if anything goes wrong, we shouldn't try balancing
return false;
}
return !collDoc[CollectionType::noBalance()].trueValue();
}
bool Grid::_inBalancingWindow( const BSONObj& balancerDoc , const boost::posix_time::ptime& now ) {
// check the 'activeWindow' marker
// if present, it is an interval during the day when the balancer should be active
// { start: "08:00" , stop: "19:30" }, strftime format is %H:%M
BSONElement windowElem = balancerDoc[SettingsType::balancerActiveWindow()];
if ( windowElem.eoo() ) {
return true;
}
// check if both 'start' and 'stop' are present
if ( ! windowElem.isABSONObj() ) {
warning() << "'activeWindow' format is { start: \"hh:mm\" , stop: ... }" << balancerDoc << endl;
return true;
}
BSONObj intervalDoc = windowElem.Obj();
const string start = intervalDoc["start"].str();
const string stop = intervalDoc["stop"].str();
if ( start.empty() || stop.empty() ) {
warning() << "must specify both start and end of balancing window: " << intervalDoc << endl;
return true;
}
// check that both 'start' and 'stop' are valid time-of-day
boost::posix_time::ptime startTime, stopTime;
if ( ! toPointInTime( start , &startTime ) || ! toPointInTime( stop , &stopTime ) ) {
warning() << "cannot parse active window (use hh:mm 24hs format): " << intervalDoc << endl;
return true;
}
LOG(1).stream() << "_inBalancingWindow: "
<< " now: " << now
<< " startTime: " << startTime
<< " stopTime: " << stopTime;
// allow balancing if during the activeWindow
// note that a window may be open during the night
if ( stopTime > startTime ) {
if ( ( now >= startTime ) && ( now <= stopTime ) ) {
return true;
}
}
else if ( startTime > stopTime ) {
if ( ( now >=startTime ) || ( now <= stopTime ) ) {
return true;
}
}
return false;
}
void Grid::flushConfig() {
boost::lock_guard lk( _lock );
_databases.clear();
}
BSONObj Grid::getConfigSetting( const std::string& name ) const {
ScopedDbConnection conn(configServer.getPrimary().getConnString(), 30);
BSONObj result = conn->findOne( SettingsType::ConfigNS,
BSON( SettingsType::key(name) ) );
conn.done();
return result;
}
Grid grid;
}