/** * 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. */ #include "mongo/platform/basic.h" #include "mongo/db/op_observer_impl.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/auth/authorization_manager_global.h" #include "mongo/db/catalog/collection_options.h" #include "mongo/db/commands/dbhash.h" #include "mongo/db/commands/feature_compatibility_version.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/s/collection_sharding_state.h" #include "mongo/db/server_options.h" #include "mongo/db/views/durable_view_catalog.h" #include "mongo/scripting/engine.h" #include "mongo/util/namespace_uuid_cache.h" #include "mongo/util/uuid_catalog.h" namespace mongo { void OpObserverImpl::onCreateIndex(OperationContext* opCtx, const NamespaceString& nss, OptionalCollectionUUID uuid, BSONObj indexDoc, bool fromMigrate) { NamespaceString systemIndexes{nss.getSystemIndexesCollection()}; if (uuid) { BSONObjBuilder builder; builder.append("createIndex", nss.coll()); for (const auto& e : indexDoc) { if (e.fieldNameStringData() != "ns"_sd) builder.append(e); } repl::logOp(opCtx, "c", nss.getCommandNS(), uuid, builder.done(), nullptr, fromMigrate); } else { repl::logOp(opCtx, "i", systemIndexes, {}, indexDoc, nullptr, fromMigrate); } AuthorizationManager::get(opCtx->getServiceContext()) ->logOp(opCtx, "i", systemIndexes, indexDoc, nullptr); auto css = CollectionShardingState::get(opCtx, systemIndexes); if (!fromMigrate) { css->onInsertOp(opCtx, indexDoc); } logOpForDbHash(opCtx, systemIndexes); } void OpObserverImpl::onInserts(OperationContext* opCtx, const NamespaceString& nss, OptionalCollectionUUID uuid, std::vector::const_iterator begin, std::vector::const_iterator end, bool fromMigrate) { repl::logOps(opCtx, "i", nss, uuid, begin, end, fromMigrate); auto css = CollectionShardingState::get(opCtx, nss.ns()); for (auto it = begin; it != end; it++) { AuthorizationManager::get(opCtx->getServiceContext())->logOp(opCtx, "i", nss, *it, nullptr); if (!fromMigrate) { css->onInsertOp(opCtx, *it); } } if (nss.ns() == FeatureCompatibilityVersion::kCollection) { for (auto it = begin; it != end; it++) { FeatureCompatibilityVersion::onInsertOrUpdate(*it); } } logOpForDbHash(opCtx, nss); if (strstr(nss.ns().c_str(), ".system.js")) { Scope::storedFuncMod(opCtx); } if (nss.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange(opCtx, nss); } } void OpObserverImpl::onUpdate(OperationContext* opCtx, const OplogUpdateEntryArgs& args) { // Do not log a no-op operation; see SERVER-21738 if (args.update.isEmpty()) { return; } repl::logOp(opCtx, "u", args.nss, args.uuid, args.update, &args.criteria, args.fromMigrate); AuthorizationManager::get(opCtx->getServiceContext()) ->logOp(opCtx, "u", args.nss, args.update, &args.criteria); auto css = CollectionShardingState::get(opCtx, args.nss); if (!args.fromMigrate) { css->onUpdateOp(opCtx, args.updatedDoc); } logOpForDbHash(opCtx, args.nss); if (strstr(args.nss.ns().c_str(), ".system.js")) { Scope::storedFuncMod(opCtx); } if (args.nss.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange(opCtx, args.nss); } if (args.nss.ns() == FeatureCompatibilityVersion::kCollection) { FeatureCompatibilityVersion::onInsertOrUpdate(args.updatedDoc); } } CollectionShardingState::DeleteState OpObserverImpl::aboutToDelete(OperationContext* opCtx, const NamespaceString& nss, const BSONObj& doc) { CollectionShardingState::DeleteState deleteState; BSONElement idElement = doc["_id"]; if (!idElement.eoo()) { deleteState.idDoc = idElement.wrap(); } auto css = CollectionShardingState::get(opCtx, nss.ns()); deleteState.isMigrating = css->isDocumentInMigratingChunk(opCtx, doc); return deleteState; } void OpObserverImpl::onDelete(OperationContext* opCtx, const NamespaceString& nss, OptionalCollectionUUID uuid, CollectionShardingState::DeleteState deleteState, bool fromMigrate) { if (deleteState.idDoc.isEmpty()) return; repl::logOp(opCtx, "d", nss, uuid, deleteState.idDoc, nullptr, fromMigrate); AuthorizationManager::get(opCtx->getServiceContext()) ->logOp(opCtx, "d", nss, deleteState.idDoc, nullptr); auto css = CollectionShardingState::get(opCtx, nss.ns()); if (!fromMigrate) { css->onDeleteOp(opCtx, deleteState); } logOpForDbHash(opCtx, nss); if (nss.coll() == "system.js") { Scope::storedFuncMod(opCtx); } if (nss.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange(opCtx, nss); } if (nss.ns() == FeatureCompatibilityVersion::kCollection) { FeatureCompatibilityVersion::onDelete(deleteState.idDoc); } } void OpObserverImpl::onOpMessage(OperationContext* opCtx, const BSONObj& msgObj) { repl::logOp(opCtx, "n", {}, {}, msgObj, nullptr, false); } void OpObserverImpl::onCreateCollection(OperationContext* opCtx, Collection* coll, const NamespaceString& collectionName, const CollectionOptions& options, const BSONObj& idIndex) { const NamespaceString dbName = collectionName.getCommandNS(); BSONObjBuilder b; b.append("create", collectionName.coll().toString()); { // Don't store the UUID as part of the options, but instead only at the top level CollectionOptions optionsToStore = options; optionsToStore.uuid.reset(); b.appendElements(optionsToStore.toBSON()); } // Include the full _id index spec in the oplog for index versions >= 2. if (!idIndex.isEmpty()) { auto versionElem = idIndex[IndexDescriptor::kIndexVersionFieldName]; invariant(versionElem.isNumber()); if (IndexDescriptor::IndexVersion::kV2 <= static_cast(versionElem.numberInt())) { b.append("idIndex", idIndex); } } BSONObj cmdObj = b.obj(); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(opCtx, "c", dbName, options.uuid, cmdObj, nullptr, false); } getGlobalAuthorizationManager()->logOp(opCtx, "c", dbName, cmdObj, nullptr); logOpForDbHash(opCtx, dbName); if (options.uuid) { UUIDCatalog& catalog = UUIDCatalog::get(opCtx->getServiceContext()); catalog.onCreateCollection(opCtx, coll, options.uuid.get()); } } namespace { /** * Given a raw collMod command object and associated collection metadata, create and return the * object for the 'o' field of a collMod oplog entry. For TTL index updates, we make sure the oplog * entry always stores the index name, instead of a key pattern. */ BSONObj makeCollModCmdObj(const BSONObj& collModCmd, const CollectionOptions& oldCollOptions, boost::optional ttlInfo) { BSONObjBuilder cmdObjBuilder; std::string ttlIndexFieldName = "index"; // Add all fields from the original collMod command. for (auto elem : collModCmd) { // We normalize all TTL collMod oplog entry objects to use the index name, even if the // command used an index key pattern. if (elem.fieldNameStringData() == ttlIndexFieldName && ttlInfo) { BSONObjBuilder ttlIndexObjBuilder; ttlIndexObjBuilder.append("name", ttlInfo->indexName); ttlIndexObjBuilder.append("expireAfterSeconds", durationCount(ttlInfo->expireAfterSeconds)); cmdObjBuilder.append(ttlIndexFieldName, ttlIndexObjBuilder.obj()); } else { cmdObjBuilder.append(elem); } } return cmdObjBuilder.obj(); } } void OpObserverImpl::onCollMod(OperationContext* opCtx, const NamespaceString& nss, OptionalCollectionUUID uuid, const BSONObj& collModCmd, const CollectionOptions& oldCollOptions, boost::optional ttlInfo) { const NamespaceString cmdNss = nss.getCommandNS(); // Create the 'o' field object. BSONObj cmdObj = makeCollModCmdObj(collModCmd, oldCollOptions, ttlInfo); // Create the 'o2' field object. We save the old collection metadata and TTL expiration. BSONObjBuilder o2Builder; o2Builder.append("collectionOptions_old", oldCollOptions.toBSON()); if (ttlInfo) { auto oldExpireAfterSeconds = durationCount(ttlInfo->oldExpireAfterSeconds); o2Builder.append("expireAfterSeconds_old", oldExpireAfterSeconds); } const BSONObj o2Obj = o2Builder.obj(); if (!nss.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(opCtx, "c", cmdNss, uuid, cmdObj, &o2Obj, false); } getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, cmdObj, nullptr); logOpForDbHash(opCtx, cmdNss); } void OpObserverImpl::onDropDatabase(OperationContext* opCtx, const std::string& dbName) { BSONObj cmdObj = BSON("dropDatabase" << 1); const NamespaceString cmdNss{dbName, "$cmd"}; repl::logOp(opCtx, "c", cmdNss, {}, cmdObj, nullptr, false); if (dbName == FeatureCompatibilityVersion::kDatabase) { FeatureCompatibilityVersion::onDropCollection(); } getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, cmdObj, nullptr); logOpForDbHash(opCtx, cmdNss); } void OpObserverImpl::onDropCollection(OperationContext* opCtx, const NamespaceString& collectionName, OptionalCollectionUUID uuid) { const NamespaceString dbName = collectionName.getCommandNS(); BSONObj cmdObj = BSON("drop" << collectionName.coll().toString()); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(opCtx, "c", dbName, uuid, cmdObj, nullptr, false); } if (collectionName.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange(opCtx, collectionName); } if (collectionName.ns() == FeatureCompatibilityVersion::kCollection) { FeatureCompatibilityVersion::onDropCollection(); } getGlobalAuthorizationManager()->logOp(opCtx, "c", dbName, cmdObj, nullptr); auto css = CollectionShardingState::get(opCtx, collectionName); css->onDropCollection(opCtx, collectionName); // Evict namespace entry from the namespace/uuid cache if it exists. NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx); cache.onDropCollection(collectionName); // Remove collection from the uuid catalog. if (uuid) { UUIDCatalog& catalog = UUIDCatalog::get(opCtx->getServiceContext()); catalog.onDropCollection(opCtx, uuid.get()); } logOpForDbHash(opCtx, dbName); } void OpObserverImpl::onDropIndex(OperationContext* opCtx, const NamespaceString& nss, OptionalCollectionUUID uuid, const std::string& indexName, const BSONObj& indexInfo) { BSONObj cmdObj = BSON("dropIndexes" << nss.coll() << "index" << indexName); auto commandNS = nss.getCommandNS(); repl::logOp(opCtx, "c", commandNS, uuid, cmdObj, &indexInfo, false); getGlobalAuthorizationManager()->logOp(opCtx, "c", commandNS, cmdObj, &indexInfo); logOpForDbHash(opCtx, commandNS); } void OpObserverImpl::onRenameCollection(OperationContext* opCtx, const NamespaceString& fromCollection, const NamespaceString& toCollection, OptionalCollectionUUID uuid, bool dropTarget, OptionalCollectionUUID dropTargetUUID, OptionalCollectionUUID dropSourceUUID, bool stayTemp) { const NamespaceString cmdNss = fromCollection.getCommandNS(); BSONObjBuilder builder; builder.append("renameCollection", fromCollection.ns()); builder.append("to", toCollection.ns()); builder.append("stayTemp", stayTemp); if (dropTargetUUID && enableCollectionUUIDs) { dropTargetUUID->appendToBuilder(&builder, "dropTarget"); } else { builder.append("dropTarget", dropTarget); } if (dropSourceUUID && enableCollectionUUIDs) { dropSourceUUID->appendToBuilder(&builder, "dropSource"); } BSONObj cmdObj = builder.done(); repl::logOp(opCtx, "c", cmdNss, uuid, cmdObj, nullptr, false); if (fromCollection.coll() == DurableViewCatalog::viewsCollectionName() || toCollection.coll() == DurableViewCatalog::viewsCollectionName()) { DurableViewCatalog::onExternalChange( opCtx, NamespaceString(DurableViewCatalog::viewsCollectionName())); } getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, cmdObj, nullptr); logOpForDbHash(opCtx, cmdNss); // Evict namespace entry from the namespace/uuid cache if it exists. NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx); cache.onRenameCollection(fromCollection); } void OpObserverImpl::onApplyOps(OperationContext* opCtx, const std::string& dbName, const BSONObj& applyOpCmd) { const NamespaceString cmdNss{dbName, "$cmd"}; repl::logOp(opCtx, "c", cmdNss, {}, applyOpCmd, nullptr, false); getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, applyOpCmd, nullptr); logOpForDbHash(opCtx, cmdNss); } void OpObserverImpl::onConvertToCapped(OperationContext* opCtx, const NamespaceString& collectionName, OptionalCollectionUUID uuid, double size) { const NamespaceString cmdNss = collectionName.getCommandNS(); BSONObj cmdObj = BSON("convertToCapped" << collectionName.coll() << "size" << size); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(opCtx, "c", cmdNss, uuid, cmdObj, nullptr, false); } getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, cmdObj, nullptr); logOpForDbHash(opCtx, cmdNss); } void OpObserverImpl::onEmptyCapped(OperationContext* opCtx, const NamespaceString& collectionName, OptionalCollectionUUID uuid) { const NamespaceString cmdNss = collectionName.getCommandNS(); BSONObj cmdObj = BSON("emptycapped" << collectionName.coll()); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(opCtx, "c", cmdNss, uuid, cmdObj, nullptr, false); } getGlobalAuthorizationManager()->logOp(opCtx, "c", cmdNss, cmdObj, nullptr); logOpForDbHash(opCtx, cmdNss); } } // namespace mongo