/**
* Copyright (C) 2016 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 .
*
* 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/bson/bsonelement_comparator.h"
#include "mongo/db/auth/action_type.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/bson/dotted_path_support.h"
#include "mongo/db/catalog/catalog_raii.h"
#include "mongo/db/catalog/index_catalog.h"
#include "mongo/db/commands.h"
#include "mongo/db/dbhelpers.h"
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/index_legacy.h"
#include "mongo/db/keypattern.h"
#include "mongo/db/query/internal_plans.h"
#include "mongo/util/log.h"
namespace mongo {
using std::string;
using std::unique_ptr;
namespace dps = ::mongo::dotted_path_support;
namespace {
class CheckShardingIndex : public ErrmsgCommandDeprecated {
public:
CheckShardingIndex() : ErrmsgCommandDeprecated("checkShardingIndex") {}
virtual void help(std::stringstream& help) const {
help << "Internal command.\n";
}
virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
return false;
}
virtual bool slaveOk() const {
return false;
}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::find);
out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
}
virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const {
return CommandHelpers::parseNsFullyQualified(dbname, cmdObj);
}
bool errmsgRun(OperationContext* opCtx,
const std::string& dbname,
const BSONObj& jsobj,
std::string& errmsg,
BSONObjBuilder& result) {
const NamespaceString nss = NamespaceString(parseNs(dbname, jsobj));
BSONObj keyPattern = jsobj.getObjectField("keyPattern");
if (keyPattern.isEmpty()) {
errmsg = "no key pattern found in checkShardingindex";
return false;
}
if (keyPattern.nFields() == 1 && str::equals("_id", keyPattern.firstElementFieldName())) {
result.appendBool("idskip", true);
return true;
}
BSONObj min = jsobj.getObjectField("min");
BSONObj max = jsobj.getObjectField("max");
if (min.isEmpty() != max.isEmpty()) {
errmsg = "either provide both min and max or leave both empty";
return false;
}
AutoGetCollection autoColl(opCtx, nss, MODE_IS);
Collection* const collection = autoColl.getCollection();
if (!collection) {
errmsg = "ns not found";
return false;
}
IndexDescriptor* idx =
collection->getIndexCatalog()->findShardKeyPrefixedIndex(opCtx,
keyPattern,
true); // requireSingleKey
if (idx == NULL) {
errmsg = "couldn't find valid index for shard key";
return false;
}
// extend min to get (min, MinKey, MinKey, ....)
KeyPattern kp(idx->keyPattern());
min = Helpers::toKeyFormat(kp.extendRangeBound(min, false));
if (max.isEmpty()) {
// if max not specified, make it (MaxKey, Maxkey, MaxKey...)
max = Helpers::toKeyFormat(kp.extendRangeBound(max, true));
} else {
// otherwise make it (max,MinKey,MinKey...) so that bound is non-inclusive
max = Helpers::toKeyFormat(kp.extendRangeBound(max, false));
}
auto exec = InternalPlanner::indexScan(opCtx,
collection,
idx,
min,
max,
BoundInclusion::kIncludeStartKeyOnly,
PlanExecutor::YIELD_AUTO,
InternalPlanner::FORWARD);
// Find the 'missingField' value used to represent a missing document field in a key of
// this index.
// NOTE A local copy of 'missingField' is made because indices may be
// invalidated during a db lock yield.
BSONObj missingFieldObj = IndexLegacy::getMissingField(opCtx, collection, idx->infoObj());
BSONElement missingField = missingFieldObj.firstElement();
// for now, the only check is that all shard keys are filled
// a 'missingField' valued index key is ok if the field is present in the document,
// TODO if $exist for nulls were picking the index, it could be used instead efficiently
int keyPatternLength = keyPattern.nFields();
RecordId loc;
BSONObj currKey;
PlanExecutor::ExecState state;
while (PlanExecutor::ADVANCED == (state = exec->getNext(&currKey, &loc))) {
// check that current key contains non missing elements for all fields in keyPattern
BSONObjIterator i(currKey);
for (int k = 0; k < keyPatternLength; k++) {
if (!i.more()) {
errmsg = str::stream() << "index key " << currKey << " too short for pattern "
<< keyPattern;
return false;
}
BSONElement currKeyElt = i.next();
const StringData::ComparatorInterface* stringComparator = nullptr;
BSONElementComparator eltCmp(BSONElementComparator::FieldNamesMode::kIgnore,
stringComparator);
if (!currKeyElt.eoo() && eltCmp.evaluate(currKeyElt != missingField))
continue;
// This is a fetch, but it's OK. The underlying code won't throw a page fault
// exception.
BSONObj obj = collection->docFor(opCtx, loc).value();
BSONObjIterator j(keyPattern);
BSONElement real;
for (int x = 0; x <= k; x++)
real = j.next();
real = dps::extractElementAtPath(obj, real.fieldName());
if (real.type())
continue;
const string msg = str::stream()
<< "found missing value in key " << redact(currKey)
<< " for doc: " << (obj.hasField("_id") ? redact(obj) : redact(obj["_id"]));
log() << "checkShardingIndex for '" << nss.toString() << "' failed: " << msg;
errmsg = msg;
return false;
}
}
if (PlanExecutor::DEAD == state || PlanExecutor::FAILURE == state) {
return CommandHelpers::appendCommandStatus(
result,
Status(ErrorCodes::OperationFailed,
str::stream() << "Executor error while checking sharding index: "
<< WorkingSetCommon::toStatusString(currKey)));
}
return true;
}
} cmdCheckShardingIndex;
} // namespace
} // namespace mongo