/** * 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 . * * 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::kReplicationRollback #include "mongo/platform/basic.h" #include "mongo/db/repl/rollback_fix_up_info.h" #include "mongo/base/string_data.h" #include "mongo/bson/simple_bsonelement_comparator.h" #include "mongo/db/jsobj.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/rollback_fix_up_info_descriptions.h" #include "mongo/db/repl/storage_interface.h" #include "mongo/util/assert_util.h" #include "mongo/util/log.h" #include "mongo/util/uuid.h" namespace mongo { namespace repl { namespace { const auto kRollbackNamespacePrefix = "local.system.rollback."_sd; } // namespace const NamespaceString RollbackFixUpInfo::kRollbackDocsNamespace(kRollbackNamespacePrefix + "docs"); const NamespaceString RollbackFixUpInfo::kRollbackCollectionUuidNamespace(kRollbackNamespacePrefix + "collectionUuid"); const NamespaceString RollbackFixUpInfo::kRollbackCollectionOptionsNamespace( kRollbackNamespacePrefix + "collectionOptions"); const NamespaceString RollbackFixUpInfo::kRollbackIndexNamespace(kRollbackNamespacePrefix + "indexes"); RollbackFixUpInfo::RollbackFixUpInfo(StorageInterface* storageInterface) : _storageInterface(storageInterface) { invariant(storageInterface); } Status RollbackFixUpInfo::processSingleDocumentOplogEntry(OperationContext* opCtx, const UUID& collectionUuid, const BSONElement& docId, SingleDocumentOpType opType, const std::string& dbName) { SingleDocumentOperationDescription desc(collectionUuid, docId, opType, dbName); auto doc = desc.toBSON(); if (SingleDocumentOpType::kInsert == opType) { // If the existing document (that may or may not exist in the "kRollbackDocsNamespace" // collection) has a 'delete' op type, this oplog entry will cancel out the previously // processed 'delete" oplog entry. We should remove the existing document from the // collection and not insert a new document. auto deleteResult = _storageInterface->deleteById(opCtx, kRollbackDocsNamespace, doc["_id"]); if (deleteResult.isOK()) { auto existingDoc = deleteResult.getValue(); if ("delete" == existingDoc["operationType"].String()) { return Status::OK(); } // Fall through and replace the 'update' op type in the existing document with 'insert' // so that the document will be dropped when we actually do the rollback. } } else if (SingleDocumentOpType::kUpdate == opType) { // If there is an existing document in the "kRollbackDocsNamespace" collection, it must // have either a 'delete' or 'update' op type. // // For a 'delete' entry, we should not replace it with 'update' so that if we process an // oplog entry with an 'insert' op type later, we can cancel out the existing entry with the // 'delete' op type. // // For an 'update' entry, there is nothing further to do because this matches the current op // type passed to this function. auto findResult = _storageInterface->findById(opCtx, kRollbackDocsNamespace, doc["_id"]); if (findResult.isOK()) { return Status::OK(); } // No existing document. Insert a new document with 'update' op type. } return _upsertById(opCtx, kRollbackDocsNamespace, doc); } Status RollbackFixUpInfo::processCreateCollectionOplogEntry(OperationContext* opCtx, const UUID& collectionUuid) { CollectionUuidDescription desc(collectionUuid, {}); auto status = _upsertById(opCtx, kRollbackCollectionUuidNamespace, desc.toBSON()); if (!status.isOK()) { return status; } // Remove all references to the collection in the create command from the other rollback // fix up collections. The documents to be removed will contain 'collectionUuid' as the _id // field or nested inside the _id field with the dotted field name '_id.collectionUuid'. // Generate key for nested _id field {_id: {collectionUuid: , ...}, ...}. BSONObjBuilder nestedIdFilterBob; collectionUuid.appendToBuilder(&nestedIdFilterBob, "_id.collectionUuid"); auto nestedIdFilter = nestedIdFilterBob.obj(); // Remove documents from the "kRollbackDocsNamespace" collection with _id's containing // 'collectionUuid': // {_id: {collectionUuid: , docId: }, ...} status = _storageInterface->deleteByFilter(opCtx, kRollbackDocsNamespace, nestedIdFilter); if (!status.isOK()) { return status; } // Generate key for _id field {_id: , ...}. BSONObjBuilder idFilterBob; collectionUuid.appendToBuilder(&idFilterBob, "_id"); auto idFilter = idFilterBob.obj(); // Remove documents from the "kRollbackCollectionOptionsNamespace" and "kRollbackIndexNamespace" // collections with the _id field set to 'collectionUuid'. // {_id: , ...} status = _storageInterface->deleteByFilter(opCtx, kRollbackCollectionOptionsNamespace, idFilter); if (!status.isOK()) { return status; } status = _storageInterface->deleteByFilter(opCtx, kRollbackIndexNamespace, nestedIdFilter); if (!status.isOK()) { return status; } return Status::OK(); } Status RollbackFixUpInfo::processDropCollectionOplogEntry(OperationContext* opCtx, const UUID& collectionUuid, const NamespaceString& nss) { CollectionUuidDescription desc(collectionUuid, nss); return _upsertById(opCtx, kRollbackCollectionUuidNamespace, desc.toBSON()); } Status RollbackFixUpInfo::processRenameCollectionOplogEntry( OperationContext* opCtx, const UUID& sourceCollectionUuid, const NamespaceString& sourceNss, boost::optional targetCollectionUuidAndNss) { CollectionUuidDescription sourceDesc(sourceCollectionUuid, sourceNss); auto status = _upsertById(opCtx, kRollbackCollectionUuidNamespace, sourceDesc.toBSON()); if (!status.isOK()) { return status; } // If target collection is not dropped during the rename operation, there is nothing further to // do. if (!targetCollectionUuidAndNss) { return Status::OK(); } CollectionUuidDescription targetDesc(targetCollectionUuidAndNss->first, targetCollectionUuidAndNss->second); return _upsertById(opCtx, kRollbackCollectionUuidNamespace, targetDesc.toBSON()); } Status RollbackFixUpInfo::processCollModOplogEntry(OperationContext* opCtx, const UUID& collectionUuid, const BSONObj& optionsObj) { // If validation is enabled for the collection, the collection options document may contain // dollar ($) prefixed field in the "validator" field. Normally, the update operator in the // query execution framework disallows such fields (see validateDollarPrefixElement() in // exec/update.cpp). To disable this check when upserting the collection options document into // the "kRollbackCollectionOptionsNamespace", we have to disable replicated writes in the // OperationContext during the update operation. UnreplicatedWritesBlock uwb(opCtx); CollectionOptionsDescription desc(collectionUuid, optionsObj); return _upsertById(opCtx, kRollbackCollectionOptionsNamespace, desc.toBSON()); } Status RollbackFixUpInfo::processCreateIndexOplogEntry(OperationContext* opCtx, const UUID& collectionUuid, const std::string& indexName) { IndexDescription desc(collectionUuid, indexName, IndexOpType::kCreate, {}); // If the existing document (that may or may not exist in the "kRollbackIndexNamespace" // collection) has a 'drop' op type, this oplog entry will cancel out the previously processed // 'dropIndexes" oplog entry. We should remove the existing document from the collection and not // insert a new document. BSONObjBuilder bob; bob.append("_id", desc.makeIdKey()); auto key = bob.obj(); auto deleteResult = _storageInterface->deleteById(opCtx, kRollbackIndexNamespace, key["_id"]); if (deleteResult.isOK()) { auto doc = deleteResult.getValue(); auto opTypeResult = IndexDescription::parseOpType(doc); if (!opTypeResult.isOK()) { invariant(ErrorCodes::FailedToParse == opTypeResult.getStatus()); warning() << "While processing createIndex oplog entry for index " << indexName << " in collection with UUID " << collectionUuid.toString() << ", found existing entry in rollback collection " << kRollbackIndexNamespace << " with unrecognized operation type:" << doc << ". Replacing existing entry."; return _upsertIndexDescription(opCtx, desc); } else if (IndexOpType::kDrop == opTypeResult.getValue()) { return Status::OK(); } // Fall through and replace existing document. } return _upsertIndexDescription(opCtx, desc); } Status RollbackFixUpInfo::processUpdateIndexTTLOplogEntry(OperationContext* opCtx, const UUID& collectionUuid, const std::string& indexName, Seconds expireAfterSeconds) { IndexDescription desc(collectionUuid, indexName, expireAfterSeconds); return _upsertIndexDescription(opCtx, desc); } Status RollbackFixUpInfo::processDropIndexOplogEntry(OperationContext* opCtx, const UUID& collectionUuid, const std::string& indexName, const BSONObj& infoObj) { IndexDescription desc(collectionUuid, indexName, IndexOpType::kDrop, infoObj); return _upsertIndexDescription(opCtx, desc); } Status RollbackFixUpInfo::_upsertById(OperationContext* opCtx, const NamespaceString& nss, const BSONObj& update) { auto key = update["_id"]; invariant(!key.eoo()); return _storageInterface->upsertById(opCtx, nss, key, update); } Status RollbackFixUpInfo::_upsertIndexDescription(OperationContext* opCtx, const IndexDescription& description) { switch (description.getOpType()) { case IndexOpType::kCreate: case IndexOpType::kDrop: return _upsertById(opCtx, kRollbackIndexNamespace, description.toBSON()); case IndexOpType::kUpdateTTL: { // For updateTTL, if there is an existing document in the collection with a "drop" op // type, we should update the index info obj in the existing document and leave the op // type unchanged. Otherwise, we assume that the existing document has an op type of // "updateTTL"; or is not present in the collection. Therefore, it is safe to overwrite // any existing data. // // It's not possible for the existing document to have a "create" op type while // processing a collMod (updateTTL) because this implies the follow sequence of // of operations in the oplog: // ..., collMod, ..., createIndex, ... // (createIndex gets processed before collMod) // This is illegal because there's a missing dropIndex oplog entry between the collMod // and createIndex oplog entries. auto expireAfterSeconds = description.getExpireAfterSeconds(); invariant(expireAfterSeconds); BSONObjBuilder updateBob; { BSONObjBuilder setOnInsertBob(updateBob.subobjStart("$setOnInsert")); setOnInsertBob.append("operationType", description.getOpTypeAsString()); } { BSONObjBuilder setBob(updateBob.subobjStart("$set")); setBob.append("infoObj.expireAfterSeconds", durationCount(*expireAfterSeconds)); } auto updateDoc = updateBob.obj(); BSONObjBuilder bob; bob.append("_id", description.makeIdKey()); auto key = bob.obj(); return _storageInterface->upsertById( opCtx, kRollbackIndexNamespace, key.firstElement(), updateDoc); } } MONGO_UNREACHABLE; } } // namespace repl } // namespace mongo