diff options
Diffstat (limited to 'src/mongo/db/repl/split_horizon.cpp')
-rw-r--r-- | src/mongo/db/repl/split_horizon.cpp | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/src/mongo/db/repl/split_horizon.cpp b/src/mongo/db/repl/split_horizon.cpp new file mode 100644 index 00000000000..10a942c632d --- /dev/null +++ b/src/mongo/db/repl/split_horizon.cpp @@ -0,0 +1,240 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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::kReplication + +#include "mongo/platform/basic.h" + +#include "mongo/db/repl/split_horizon.h" + +#include <utility> + +#include "mongo/bson/util/bson_extract.h" +#include "mongo/db/client.h" +#include "mongo/util/log.h" + +using namespace std::literals::string_literals; + +using std::begin; +using std::end; + +namespace mongo { +namespace repl { +namespace { +const auto getSplitHorizonParameters = Client::declareDecoration<SplitHorizon::Parameters>(); + +using AllMappings = SplitHorizon::AllMappings; + +// The reverse mappings for a forward mapping are used to fully initialize a `SplitHorizon` +// instance. +AllMappings computeReverseMappings(SplitHorizon::ForwardMapping forwardMapping) { + using ForwardMapping = SplitHorizon::ForwardMapping; + using ReverseHostOnlyMapping = SplitHorizon::ReverseHostOnlyMapping; + + // Build the reverse mapping (from host-only to horizon names) from the forward mapping table. + ReverseHostOnlyMapping reverseHostMapping; + + // Default horizon case is special -- it always has to exist, and needs to be set before + // entering the loop, to correctly handle ambiguous host-only cases within that horizon. + reverseHostMapping.emplace(forwardMapping[SplitHorizon::kDefaultHorizon].host(), + std::string{SplitHorizon::kDefaultHorizon}); + for (const auto& entry : forwardMapping) { + reverseHostMapping[entry.second.host()] = entry.first; + } + + // Check for duplicate host-and-port entries. + if (forwardMapping.size() != reverseHostMapping.size()) { + const auto horizonMember = [&] { + std::vector<std::string> rv; + std::transform(begin(forwardMapping), + end(forwardMapping), + back_inserter(rv), + [](const auto& entry) { return entry.second.host(); }); + std::sort(begin(rv), end(rv)); + return rv; + }(); + + auto duplicate = std::adjacent_find(begin(horizonMember), end(horizonMember)); + invariant(duplicate != end(horizonMember)); + + uasserted(ErrorCodes::BadValue, "Duplicate horizon member found \""s + *duplicate + "\"."); + } + + + return {std::move(forwardMapping), std::move(reverseHostMapping)}; +} + +SplitHorizon::ForwardMapping computeForwardMappings( + const HostAndPort& host, const boost::optional<BSONElement>& horizonsElement) { + SplitHorizon::ForwardMapping forwardMapping; + + if (horizonsElement) { + using MapMember = std::pair<std::string, HostAndPort>; + + if (horizonsElement->eoo()) { + uasserted(ErrorCodes::BadValue, + str::stream() << "The horizons field cannot be empty, if present."); + } + + if (horizonsElement->type() != Object) { + uasserted(ErrorCodes::TypeMismatch, + str::stream() << "The horizons field must be an object"); + } + + const auto& horizonsObject = horizonsElement->Obj(); + + if (horizonsObject.isEmpty()) { + uasserted(ErrorCodes::BadValue, + str::stream() << "The horizons field cannot be empty, if present."); + } + + // Process all of the BSON description of horizons into a linear list. + auto convert = [](auto&& horizonObj) -> MapMember { + const StringData horizonName = horizonObj.fieldName(); + + if (horizonObj.type() != String) { + uasserted(ErrorCodes::TypeMismatch, + str::stream() << "horizons." << horizonName + << " field has non-string value of type " + << typeName(horizonObj.type())); + } else if (horizonName == SplitHorizon::kDefaultHorizon) { + uasserted(ErrorCodes::BadValue, + "Horizon name \"" + SplitHorizon::kDefaultHorizon + + "\" is reserved for internal mongodb usage"); + } else if (horizonName == "") { + uasserted(ErrorCodes::BadValue, "Horizons cannot have empty names"); + } + + return {horizonName.toString(), HostAndPort{horizonObj.valueStringData()}}; + }; + + const auto horizonEntries = [&] { + std::vector<MapMember> rv; + std::transform( + begin(horizonsObject), end(horizonsObject), inserter(rv, end(rv)), convert); + return rv; + }(); + + + // Dump the linear list into the forward mapping. + forwardMapping.insert(begin(horizonEntries), end(horizonEntries)); + + // Check for duplicate horizon names and reserved names, which would be if the horizon + // linear list size disagrees with the size of the mapping. + if (horizonEntries.size() != forwardMapping.size()) { + // If the map has a different amount than a linear list of the bson converted, then it + // had better be a lesser amount, indicating duplicates. A greater amount should be + // impossible. + invariant(horizonEntries.size() > forwardMapping.size()); + + // Find which one is duplicated. + const auto horizonNames = [&] { + std::vector<std::string> rv; + std::transform(begin(horizonEntries), + end(horizonEntries), + back_inserter(rv), + [](const auto& entry) { return entry.first; }); + std::sort(begin(rv), end(rv)); + return rv; + }(); + + const auto duplicate = std::adjacent_find(begin(horizonNames), end(horizonNames)); + + // Report our duplicate if found. + if (duplicate != end(horizonNames)) { + uasserted(ErrorCodes::BadValue, + "Duplicate horizon name found \""s + *duplicate + "\"."); + } + } + } + + // Finally add the default mapping, regardless of whether we processed a configuration. + const bool successInDefaultPlacement = + forwardMapping.emplace(SplitHorizon::kDefaultHorizon, host).second; + // And that insertion BETTER succeed -- it mightn't if there's a bug in the configuration + // processing logic. + invariant(successInDefaultPlacement); + + return forwardMapping; +} +} // namespace + +void SplitHorizon::setParameters(Client* const client, boost::optional<std::string> sniName) { + stdx::lock_guard<Client> lk(*client); + getSplitHorizonParameters(*client) = Parameters{std::move(sniName)}; +} + +auto SplitHorizon::getParameters(const Client* const client) -> Parameters { + return getSplitHorizonParameters(*client); +} + +StringData SplitHorizon::determineHorizon(const SplitHorizon::Parameters& horizonParameters) const { + if (horizonParameters.sniName) { + const auto sniName = *horizonParameters.sniName; + const auto found = _reverseHostMapping.find(sniName); + if (found != end(_reverseHostMapping)) { + return found->second; + } + } + return kDefaultHorizon; +} + +void SplitHorizon::toBSON(BSONObjBuilder& configBuilder) const { + invariant(!_forwardMapping.empty()); + invariant(_forwardMapping.count(SplitHorizon::kDefaultHorizon)); + + // `forwardMapping` should always contain the "__default" horizon, so we need to emit the + // horizon repl specification only when there are OTHER horizons besides it. If there's only + // one horizon, it's gotta be "__default", so we do nothing. + if (_forwardMapping.size() == 1) + return; + + BSONObjBuilder horizonsBson(configBuilder.subobjStart("horizons")); + for (const auto& horizon : _forwardMapping) { + // The "__default" horizon should never be emitted in the horizon table. + if (horizon.first == SplitHorizon::kDefaultHorizon) + continue; + horizonsBson.append(horizon.first, horizon.second.toString()); + } +} + +// A split horizon built from a known forward mapping table should just need to construct the +// reverse mappings. +SplitHorizon::SplitHorizon(ForwardMapping mapping) + : SplitHorizon(computeReverseMappings(std::move(mapping))) {} + +// A split horizon constructed from the BSON configuration and the host specifier for this member +// needs to compute the forward mapping table. In turn that will be used to compute the reverse +// mapping table. +SplitHorizon::SplitHorizon(const HostAndPort& host, + const boost::optional<BSONElement>& horizonsElement) + : SplitHorizon(computeForwardMappings(host, horizonsElement)) {} + +} // namespace repl +} // namespace mongo |