/**
* Copyright (C) 2012 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 .
*/
#include "mongo/s/config_upgrade.h"
#include
#include "mongo/base/init.h"
#include "mongo/client/dbclientcursor.h"
#include "mongo/client/distlock.h"
#include "mongo/s/cluster_client_internal.h"
#include "mongo/s/mongo_version_range.h"
#include "mongo/s/type_config_version.h"
#include "mongo/s/type_database.h"
#include "mongo/s/type_shard.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/version.h"
namespace mongo {
using mongoutils::str::stream;
//
// BEGIN CONFIG UPGRADE REGISTRATION
//
struct VersionRange {
VersionRange(int _minCompatibleVersion, int _currentVersion) :
minCompatibleVersion(_minCompatibleVersion), currentVersion(_currentVersion)
{
}
bool operator==(const VersionRange& other) const {
return (other.minCompatibleVersion == minCompatibleVersion)
&& (other.currentVersion == currentVersion);
}
bool operator!=(const VersionRange& other) const {
return !(*this == other);
}
int minCompatibleVersion;
int currentVersion;
};
/**
* Encapsulates the information needed to register a config upgrade.
*/
struct Upgrade {
typedef boost::function UpgradeCallback;
Upgrade(int _fromVersion,
const VersionRange& _toVersionRange,
UpgradeCallback _upgradeCallback) :
fromVersion(_fromVersion),
toVersionRange(_toVersionRange),
upgradeCallback(_upgradeCallback)
{
}
// The config version we're upgrading from
int fromVersion;
// The config version we're upgrading to and the min compatible config version (min, to)
VersionRange toVersionRange;
// The upgrade callback which performs the actual upgrade
UpgradeCallback upgradeCallback;
};
typedef map ConfigUpgradeRegistry;
void validateRegistry(const ConfigUpgradeRegistry& registry);
/**
* Creates a registry of config upgrades used by the code below.
*
* MODIFY THIS CODE HERE TO CREATE A NEW UPGRADE PATH FROM X to Y
* YOU MUST ALSO MODIFY THE VERSION DECLARATIONS IN config_upgrade.h
*
* Caveats:
* - All upgrade paths must eventually lead to the exact same version range of
* min and max compatible versions.
* - This resulting version range must be equal to:
* make_pair(MIN_COMPATIBLE_CONFIG_VERSION, CURRENT_CONFIG_VERSION)
* - There must always be an upgrade path from the empty version (0) to the latest
* config version.
*
* If any of the above is false, we fassert and fail to start.
*/
ConfigUpgradeRegistry createRegistry() {
ConfigUpgradeRegistry registry;
// v0 to v4
Upgrade v0ToV4(0, VersionRange(3, 4), doUpgradeV0ToV4);
registry.insert(make_pair(v0ToV4.fromVersion, v0ToV4));
// v3 to v4
Upgrade v3ToV4(3, VersionRange(3, 4), doUpgradeV3ToV4);
registry.insert(make_pair(v3ToV4.fromVersion, v3ToV4));
validateRegistry(registry);
return registry;
}
/**
* Does a sanity-check validation of the registry ensuring three things:
* 1. All upgrade paths lead to the same minCompatible/currentVersion
* 2. Our constants match this final version pair
* 3. There is a zero-version upgrade path
*/
void validateRegistry(const ConfigUpgradeRegistry& registry) {
VersionRange maxCompatibleConfigVersionRange(-1, -1);
bool hasZeroVersionUpgrade = false;
for (ConfigUpgradeRegistry::const_iterator it = registry.begin(); it != registry.end();
++it)
{
const Upgrade& upgrade = it->second;
if (upgrade.fromVersion == 0) hasZeroVersionUpgrade = true;
if (maxCompatibleConfigVersionRange.currentVersion
< upgrade.toVersionRange.currentVersion)
{
maxCompatibleConfigVersionRange = upgrade.toVersionRange;
}
else if (maxCompatibleConfigVersionRange.currentVersion
== upgrade.toVersionRange.currentVersion)
{
// Make sure all max upgrade paths end up with same version and compatibility
fassert(16621, maxCompatibleConfigVersionRange == upgrade.toVersionRange);
}
}
// Make sure we have a zero-version upgrade
fassert(16622, hasZeroVersionUpgrade);
// Make sure our max registered range is the same as our constants
fassert(16623,
maxCompatibleConfigVersionRange
== VersionRange(MIN_COMPATIBLE_CONFIG_VERSION, CURRENT_CONFIG_VERSION));
}
//
// END CONFIG UPGRADE REGISTRATION
//
// Gets the config version information from the config server
Status getConfigVersion(const ConnectionString& configLoc, VersionType* versionInfo) {
scoped_ptr connPtr;
try {
versionInfo->clear();
connPtr.reset(ScopedDbConnection::getInternalScopedDbConnection(configLoc, 30));
ScopedDbConnection& conn = *connPtr;
scoped_ptr cursor(_safeCursor(conn->query("config.version",
BSONObj())));
bool hasConfigData = conn->count(ShardType::ConfigNS)
|| conn->count(DatabaseType::ConfigNS)
|| conn->count(CollectionType::ConfigNS);
if (!cursor->more()) {
// Version is 1 if we have data, 0 if we're completely empty
if (hasConfigData) {
versionInfo->setMinCompatibleVersion(UpgradeHistory_UnreportedVersion);
versionInfo->setCurrentVersion(UpgradeHistory_UnreportedVersion);
}
else {
versionInfo->setMinCompatibleVersion(UpgradeHistory_EmptyVersion);
versionInfo->setCurrentVersion(UpgradeHistory_EmptyVersion);
}
connPtr->done();
return Status::OK();
}
BSONObj versionDoc = cursor->next();
string errMsg;
if (!versionInfo->parseBSON(versionDoc, &errMsg) || !versionInfo->isValid(&errMsg)) {
connPtr->done();
return Status(ErrorCodes::UnsupportedFormat,
stream() << "invalid config version document " << versionDoc
<< causedBy(errMsg));
}
if (cursor->more()) {
connPtr->done();
return Status(ErrorCodes::RemoteValidationError,
stream() << "should only have 1 document "
<< "in config.version collection");
}
}
catch (const DBException& e) {
return e.toStatus();
}
connPtr->done();
return Status::OK();
}
// Checks version compatibility with our version
VersionStatus isConfigVersionCompatible(const VersionType& versionInfo, string* whyNot) {
string dummy;
if (!whyNot) whyNot = &dummy;
// Check if we're empty
if (versionInfo.getCurrentVersion() == UpgradeHistory_EmptyVersion) {
return VersionStatus_NeedUpgrade;
}
// Check that we aren't too old
if (CURRENT_CONFIG_VERSION < versionInfo.getMinCompatibleVersion()) {
*whyNot = stream() << "not compatible with current config version "
<< versionInfo.getMinCompatibleVersion() << ", our version is "
<< CURRENT_CONFIG_VERSION;
return VersionStatus_Incompatible;
}
// Check that we aren't excluded
vector excludedRanges;
if (!MongoVersionRange::parseBSONArray(versionInfo.getExcludedRanges(),
&excludedRanges,
whyNot))
{
*whyNot = stream() << "could not understand excluded version ranges"
<< causedBy(whyNot);
return VersionStatus_Incompatible;
}
// versionString is the global version of this process
if (isInMongoVersionRanges(versionString, excludedRanges)) {
*whyNot = stream() << "not compatible with current config version, " << "version "
<< versionString << "has been excluded.";
return VersionStatus_Incompatible;
}
// Check if we need to upgrade
if (versionInfo.getCurrentVersion() >= CURRENT_CONFIG_VERSION) {
return VersionStatus_Compatible;
}
return VersionStatus_NeedUpgrade;
}
// Dispatches upgrades based on version to the upgrades registered in the upgrade registry
bool _nextUpgrade(const ConnectionString& configLoc,
const ConfigUpgradeRegistry& registry,
const VersionType& lastVersionInfo,
VersionType* upgradedVersionInfo,
string* errMsg)
{
int fromVersion = lastVersionInfo.getCurrentVersion();
ConfigUpgradeRegistry::const_iterator foundIt = registry.find(fromVersion);
if (foundIt == registry.end()) {
*errMsg = stream() << "newer version " << CURRENT_CONFIG_VERSION
<< " of mongo config metadata is required, " << "current version is "
<< fromVersion << ", "
<< "don't know how to upgrade from this version";
return false;
}
const Upgrade& upgrade = foundIt->second;
int toVersion = upgrade.toVersionRange.currentVersion;
log() << "starting next upgrade step from v" << fromVersion << " to v" << toVersion << endl;
// Log begin to config.changelog
Status logStatus = logConfigChange(configLoc,
"",
VersionType::ConfigNS,
"starting upgrade of config database",
BSON("from" << fromVersion << "to" << toVersion));
if (!logStatus.isOK()) {
*errMsg = stream() << "could not write initial changelog entry for upgrade"
<< causedBy(logStatus);
return false;
}
if (!upgrade.upgradeCallback(configLoc, lastVersionInfo, errMsg)) {
*errMsg = stream() << "error upgrading config database from v" << fromVersion << " to v"
<< toVersion << causedBy(errMsg);
return false;
}
// Get the config version we've upgraded to
// This can't be done in _nextUpgrade, since
Status verifyConfigStatus = getConfigVersion(configLoc, upgradedVersionInfo);
if (!verifyConfigStatus.isOK()) {
*errMsg = stream() << "failed to validate v" << fromVersion << " config version upgrade"
<< causedBy(verifyConfigStatus);
return false;
}
// Log end to config.changelog
logStatus = logConfigChange(configLoc,
"",
VersionType::ConfigNS,
"finished upgrade of config database",
BSON("from" << fromVersion << "to" << toVersion));
if (!logStatus.isOK()) {
*errMsg = stream() << "could not write final changelog entry for upgrade"
<< causedBy(logStatus);
return false;
}
return true;
}
// Upgrades the config server
bool checkAndUpgradeConfigVersion(const ConnectionString& configLoc,
bool upgrade,
VersionType* initialVersionInfo,
VersionType* versionInfo,
string* errMsg)
{
string dummy;
if (!errMsg) errMsg = &dummy;
//
// Check compatibility of config version
//
Status getConfigStatus = getConfigVersion(configLoc, versionInfo);
if (!getConfigStatus.isOK()) {
*errMsg = stream() << "could not load config version for upgrade"
<< causedBy(getConfigStatus);
return false;
}
versionInfo->cloneTo(initialVersionInfo);
VersionStatus comp = isConfigVersionCompatible(*versionInfo, errMsg);
if (comp == VersionStatus_Incompatible) return false;
if (comp == VersionStatus_Compatible) return true;
verify(comp == VersionStatus_NeedUpgrade);
//
// Our current config version is now greater than the current version, so we should upgrade
// if possible.
//
// First check for the upgrade flag
if (!versionInfo->getCurrentVersion() == UpgradeHistory_EmptyVersion && !upgrade) {
*errMsg = stream() << "newer version " << CURRENT_CONFIG_VERSION
<< " of mongo config metadata is required, " << "current version is "
<< versionInfo->getCurrentVersion() << ", "
<< "need to run mongos with --upgrade";
return false;
}
//
// Acquire a lock for the upgrade process.
//
// We want to ensure that only a single mongo process is upgrading the config server at a
// time.
//
ScopedDistributedLock upgradeLock(configLoc, "configUpgrade");
upgradeLock.setLockMessage(stream() << "upgrading config database to new format v"
<< CURRENT_CONFIG_VERSION);
if (!upgradeLock.acquire(15 * 60 * 1000, errMsg)) {
*errMsg = stream() << "could not acquire upgrade lock for config upgrade to v"
<< CURRENT_CONFIG_VERSION << causedBy(errMsg);
return false;
}
//
// Double-check compatibility inside the upgrade lock
// Another process may have won the lock earlier and done the upgrade for us, check
// if this is the case.
//
getConfigStatus = getConfigVersion(configLoc, versionInfo);
if (!getConfigStatus.isOK()) {
*errMsg = stream() << "could not reload config version for upgrade"
<< causedBy(getConfigStatus);
return false;
}
versionInfo->cloneTo(initialVersionInfo);
comp = isConfigVersionCompatible(*versionInfo, errMsg);
if (comp == VersionStatus_Incompatible) return false;
if (comp == VersionStatus_Compatible) return true;
verify(comp == VersionStatus_NeedUpgrade);
//
// Run through the upgrade steps necessary to bring our config version to the current
// version
//
log() << "starting upgrade of config server from v" << versionInfo->getCurrentVersion()
<< " to v" << CURRENT_CONFIG_VERSION << endl;
ConfigUpgradeRegistry registry(createRegistry());
while (versionInfo->getCurrentVersion() < CURRENT_CONFIG_VERSION) {
int fromVersion = versionInfo->getCurrentVersion();
//
// Run the next upgrade process and replace versionInfo with the result of the
// upgrade.
//
if (!_nextUpgrade(configLoc, registry, *versionInfo, versionInfo, errMsg)) {
return false;
}
// Ensure we're making progress here
if (versionInfo->getCurrentVersion() <= fromVersion) {
*errMsg = stream() << "bad v" << fromVersion << " config version upgrade, "
<< "version did not increment and is now "
<< versionInfo->getCurrentVersion();
return false;
}
}
verify(versionInfo->getCurrentVersion() == CURRENT_CONFIG_VERSION);
log() << "upgrade of config server to v" << versionInfo->getCurrentVersion()
<< " successful" << endl;
return true;
}
}