diff options
Diffstat (limited to 'src/mongo/s/commands/cluster_split_collection_cmd.cpp')
-rw-r--r-- | src/mongo/s/commands/cluster_split_collection_cmd.cpp | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/src/mongo/s/commands/cluster_split_collection_cmd.cpp b/src/mongo/s/commands/cluster_split_collection_cmd.cpp new file mode 100644 index 00000000000..ce8795a67ef --- /dev/null +++ b/src/mongo/s/commands/cluster_split_collection_cmd.cpp @@ -0,0 +1,288 @@ +/** + * Copyright (C) 2015 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::kCommand + +#include "mongo/platform/basic.h" + +#include <string> +#include <vector> + +#include "mongo/db/auth/action_set.h" +#include "mongo/db/auth/action_type.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/client.h" +#include "mongo/db/commands.h" +#include "mongo/db/field_parser.h" +#include "mongo/s/client/shard_connection.h" +#include "mongo/s/chunk_manager.h" +#include "mongo/s/grid.h" +#include "mongo/util/log.h" + +namespace mongo { + + using std::string; + using std::vector; + +namespace { + + class SplitCollectionCmd : public Command { + public: + SplitCollectionCmd() : Command("split", false, "split") { } + + virtual bool slaveOk() const { + return true; + } + + virtual bool adminOnly() const { + return true; + } + + virtual bool isWriteCommandForConfigServer() const { + return false; + } + + virtual void help(std::stringstream& help) const { + help << " example: - split the shard that contains give key\n" + << " { split : 'alleyinsider.blog.posts' , find : { ts : 1 } }\n" + << " example: - split the shard that contains the key with this as the middle\n" + << " { split : 'alleyinsider.blog.posts' , middle : { ts : 1 } }\n" + << " NOTE: this does not move the chunks, it just creates a logical separation."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + + if (!client->getAuthorizationSession()->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace( + NamespaceString(parseNs(dbname, + cmdObj))), + ActionType::splitChunk)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + virtual std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const { + return parseNsFullyQualified(dbname, cmdObj); + } + + virtual bool run(OperationContext* txn, + const std::string& dbname, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + + if (!configServer.allUp(false, errmsg)) { + return false; + } + + ShardConnection::sync(); + + const std::string ns = parseNs(dbname, cmdObj); + if (ns.size() == 0) { + errmsg = "no ns"; + return false; + } + + DBConfigPtr config = grid.getDBConfig(ns); + if (!config->isSharded(ns)) { + config->reload(); + + if (!config->isSharded(ns)) { + errmsg = "ns not sharded. have to shard before can split"; + return false; + } + } + + const BSONField<BSONObj> findField("find", BSONObj()); + const BSONField<BSONArray> boundsField("bounds", BSONArray()); + const BSONField<BSONObj> middleField("middle", BSONObj()); + + BSONObj find; + if (FieldParser::extract(cmdObj, findField, &find, &errmsg) == + FieldParser::FIELD_INVALID) { + return false; + } + + BSONArray bounds; + if (FieldParser::extract(cmdObj, boundsField, &bounds, &errmsg) == + FieldParser::FIELD_INVALID) { + return false; + } + + if (!bounds.isEmpty()) { + if (!bounds.hasField("0")) { + errmsg = "lower bound not specified"; + return false; + } + + if (!bounds.hasField("1")) { + errmsg = "upper bound not specified"; + return false; + } + } + + if (!find.isEmpty() && !bounds.isEmpty()) { + errmsg = "cannot specify bounds and find at the same time"; + return false; + } + + BSONObj middle; + if (FieldParser::extract(cmdObj, middleField, &middle, &errmsg) == + FieldParser::FIELD_INVALID) { + return false; + } + + if (find.isEmpty() && bounds.isEmpty() && middle.isEmpty()) { + errmsg = "need to specify find/bounds or middle"; + return false; + } + + if (!find.isEmpty() && !middle.isEmpty()) { + errmsg = "cannot specify find and middle together"; + return false; + } + + if (!bounds.isEmpty() && !middle.isEmpty()) { + errmsg = "cannot specify bounds and middle together"; + return false; + } + + // This refreshes the chunk metadata if stale. + ChunkManagerPtr info = config->getChunkManager(ns, true); + ChunkPtr chunk; + + if (!find.isEmpty()) { + StatusWith<BSONObj> status = + info->getShardKeyPattern().extractShardKeyFromQuery(find); + + // Bad query + if (!status.isOK()) { + return appendCommandStatus(result, status.getStatus()); + } + + BSONObj shardKey = status.getValue(); + if (shardKey.isEmpty()) { + errmsg = stream() << "no shard key found in chunk query " << find; + return false; + } + + chunk = info->findIntersectingChunk(shardKey); + invariant(chunk.get()); + } + else if (!bounds.isEmpty()) { + + if (!info->getShardKeyPattern().isShardKey(bounds[0].Obj()) + || !info->getShardKeyPattern().isShardKey(bounds[1].Obj())) { + errmsg = stream() << "shard key bounds " << "[" << bounds[0].Obj() << "," + << bounds[1].Obj() << ")" + << " are not valid for shard key pattern " + << info->getShardKeyPattern().toBSON(); + return false; + } + + BSONObj minKey = info->getShardKeyPattern().normalizeShardKey(bounds[0].Obj()); + BSONObj maxKey = info->getShardKeyPattern().normalizeShardKey(bounds[1].Obj()); + + chunk = info->findIntersectingChunk(minKey); + invariant(chunk.get()); + + if (chunk->getMin().woCompare(minKey) != 0 + || chunk->getMax().woCompare(maxKey) != 0) { + errmsg = stream() << "no chunk found with the shard key bounds " << "[" + << minKey << "," << maxKey << ")"; + return false; + } + } + else { + // Middle + if (!info->getShardKeyPattern().isShardKey(middle)) { + errmsg = stream() << "new split key " << middle + << " is not valid for shard key pattern " + << info->getShardKeyPattern().toBSON(); + return false; + } + + middle = info->getShardKeyPattern().normalizeShardKey(middle); + + // Check shard key size when manually provided + Status status = ShardKeyPattern::checkShardKeySize(middle); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + + chunk = info->findIntersectingChunk(middle); + invariant(chunk.get()); + + if (chunk->getMin().woCompare(middle) == 0 + || chunk->getMax().woCompare(middle) == 0) { + errmsg = stream() << "new split key " << middle + << " is a boundary key of existing chunk " << "[" + << chunk->getMin() << "," << chunk->getMax() << ")"; + return false; + } + } + + invariant(chunk.get()); + log() << "splitting chunk [" << chunk->getMin() << "," << chunk->getMax() << ")" + << " in collection " << ns + << " on shard " << chunk->getShard().getName(); + + BSONObj res; + if (middle.isEmpty()) { + Status status = chunk->split(Chunk::atMedian, NULL, NULL); + if (!status.isOK()) { + errmsg = "split failed"; + result.append("cause", status.toString()); + return false; + } + } + else { + vector<BSONObj> splitPoints; + splitPoints.push_back(middle); + + Status status = chunk->multiSplit(splitPoints, NULL); + if (!status.isOK()) { + errmsg = "split failed"; + result.append("cause", status.toString()); + return false; + } + } + + return true; + } + + } splitCollectionCmd; + +} // namespace +} // namespace mongo |