/* * ConfigBroadcaster.actor.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <algorithm> #include "fdbserver/ConfigBroadcaster.h" #include "fdbserver/Knobs.h" #include "fdbserver/IConfigConsumer.h" #include "flow/UnitTest.h" #include "flow/actorcompiler.h" // must be last include namespace { bool matchesConfigClass(ConfigClassSet const& configClassSet, Optional<KeyRef> configClass) { return !configClass.present() || configClassSet.contains(configClass.get()); } // Helper functions for STL containers, with flow-friendly error handling template <class MapContainer, class K> auto get(MapContainer& m, K const& k) -> decltype(m.at(k)) { auto it = m.find(k); ASSERT(it != m.end()); return it->second; } template <class Container, class K> void remove(Container& container, K const& k) { auto it = container.find(k); ASSERT(it != container.end()); container.erase(it); } } // namespace class ConfigBroadcasterImpl { // Holds information about each client connected to the broadcaster. struct BroadcastClientDetails { // Triggered when the worker dies. Future<Void> watcher; ConfigClassSet configClassSet; Version lastSeenVersion; ConfigBroadcastInterface broadcastInterface; bool operator==(BroadcastClientDetails const& rhs) const { return configClassSet == rhs.configClassSet && lastSeenVersion == rhs.lastSeenVersion && broadcastInterface == rhs.broadcastInterface; } bool operator!=(BroadcastClientDetails const& rhs) const { return !(*this == rhs); } BroadcastClientDetails() = default; BroadcastClientDetails(Future<Void> watcher, ConfigClassSet const& configClassSet, Version lastSeenVersion, ConfigBroadcastInterface broadcastInterface) : watcher(watcher), configClassSet(configClassSet), lastSeenVersion(lastSeenVersion), broadcastInterface(broadcastInterface) {} }; ConfigDBType configDBType; std::map<ConfigKey, KnobValue> snapshot; std::deque<VersionedConfigMutation> mutationHistory; std::deque<VersionedConfigCommitAnnotation> annotationHistory; Version lastCompactedVersion; Version mostRecentVersion; std::unique_ptr<IConfigConsumer> consumer; Future<Void> consumerFuture; ActorCollection actors{ false }; std::map<UID, BroadcastClientDetails> clients; std::map<UID, Future<Void>> clientFailures; UID id; CounterCollection cc; Counter compactRequest; Counter successfulChangeRequest; Counter failedChangeRequest; Counter snapshotRequest; Future<Void> logger; int coordinators = 0; std::unordered_set<NetworkAddress> activeConfigNodes; bool disallowUnregistered = false; Promise<Void> newConfigNodesAllowed; Future<Void> pushSnapshot(Version snapshotVersion, BroadcastClientDetails const& client) { if (client.lastSeenVersion >= snapshotVersion) { return Void(); } ++snapshotRequest; ConfigBroadcastSnapshotRequest request; for (const auto& [key, value] : snapshot) { if (matchesConfigClass(client.configClassSet, key.configClass)) { request.snapshot[key] = value; } } request.version = snapshotVersion; TraceEvent(SevDebug, "ConfigBroadcasterSnapshotRequest", id) .detail("Size", request.snapshot.size()) .detail("Version", request.version); return success(brokenPromiseToNever(client.broadcastInterface.snapshot.getReply(request))); } template <class Changes> Future<Void> pushChanges(BroadcastClientDetails& client, Changes const& changes) { // Skip if client has already seen the latest version. if (client.lastSeenVersion >= mostRecentVersion) { return Void(); } ConfigBroadcastChangesRequest req; for (const auto& versionedMutation : changes) { if (versionedMutation.version > client.lastSeenVersion && matchesConfigClass(client.configClassSet, versionedMutation.mutation.getConfigClass())) { TraceEvent te(SevDebug, "ConfigBroadcasterSendingChangeMutation", id); te.detail("Version", versionedMutation.version) .detail("ReqLastSeenVersion", client.lastSeenVersion) .detail("ConfigClass", versionedMutation.mutation.getConfigClass()) .detail("KnobName", versionedMutation.mutation.getKnobName()) .detail("ClientID", client.broadcastInterface.id()); if (versionedMutation.mutation.isSet()) { te.detail("Op", "Set").detail("KnobValue", versionedMutation.mutation.getValue().toString()); } else { te.detail("Op", "Clear"); } req.changes.push_back_deep(req.changes.arena(), versionedMutation); } } if (req.changes.size() == 0) { return Void(); } client.lastSeenVersion = mostRecentVersion; req.mostRecentVersion = mostRecentVersion; ++successfulChangeRequest; return success(client.broadcastInterface.changes.getReply(req)); } ConfigBroadcasterImpl() : lastCompactedVersion(0), mostRecentVersion(0), id(deterministicRandom()->randomUniqueID()), cc("ConfigBroadcaster"), compactRequest("CompactRequest", cc), successfulChangeRequest("SuccessfulChangeRequest", cc), failedChangeRequest("FailedChangeRequest", cc), snapshotRequest("SnapshotRequest", cc) { logger = traceCounters( "ConfigBroadcasterMetrics", id, SERVER_KNOBS->WORKER_LOGGING_INTERVAL, &cc, "ConfigBroadcasterMetrics"); } void addChanges(Standalone<VectorRef<VersionedConfigMutationRef>> const& changes, Version mostRecentVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> const& annotations) { this->mostRecentVersion = mostRecentVersion; mutationHistory.insert(mutationHistory.end(), changes.begin(), changes.end()); annotationHistory.insert(annotationHistory.end(), annotations.begin(), annotations.end()); for (const auto& change : changes) { const auto& mutation = change.mutation; if (mutation.isSet()) { snapshot[mutation.getKey()] = mutation.getValue(); } else { snapshot.erase(mutation.getKey()); } } for (auto& [id, client] : clients) { actors.add(brokenPromiseToNever(pushChanges(client, changes))); } } template <class Snapshot> Future<Void> setSnapshot(Snapshot&& snapshot, Version snapshotVersion) { this->snapshot = std::forward<Snapshot>(snapshot); this->lastCompactedVersion = snapshotVersion; std::vector<Future<Void>> futures; for (const auto& [id, client] : clients) { futures.push_back(brokenPromiseToNever(pushSnapshot(snapshotVersion, client))); } return waitForAll(futures); } ACTOR template <class Snapshot> static Future<Void> pushSnapshotAndChanges(ConfigBroadcasterImpl* self, Snapshot snapshot, Version snapshotVersion, Standalone<VectorRef<VersionedConfigMutationRef>> changes, Version changesVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> annotations) { // Make sure all snapshot messages were received before sending changes. wait(self->setSnapshot(snapshot, snapshotVersion)); self->addChanges(changes, changesVersion, annotations); return Void(); } ACTOR static Future<Void> waitForFailure(ConfigBroadcasterImpl* self, Future<Void> watcher, UID clientUID, NetworkAddress clientAddress) { wait(watcher); TraceEvent(SevDebug, "ConfigBroadcastClientDied", self->id) .detail("ClientID", clientUID) .detail("Address", clientAddress); self->clients.erase(clientUID); self->clientFailures.erase(clientUID); self->activeConfigNodes.erase(clientAddress); // See comment where this promise is reset below. if (self->newConfigNodesAllowed.isSet()) { self->newConfigNodesAllowed.reset(); } return Void(); } // Determines whether the registering ConfigNode is allowed to start // serving configuration database requests and snapshot data. In order to // ensure strict serializability, some nodes may be temporarily restricted // from participation until the other nodes in the system are brought up to // date. ACTOR static Future<Void> registerNodeInternal(ConfigBroadcasterImpl* self, WorkerInterface w, Version lastSeenVersion) { if (self->configDBType == ConfigDBType::SIMPLE) { wait(success(retryBrokenPromise(w.configBroadcastInterface.ready, ConfigBroadcastReadyRequest{}))); return Void(); } state NetworkAddress address = w.address(); // Ask the registering ConfigNode whether it has registered in the past. ConfigBroadcastRegisteredReply reply = wait(w.configBroadcastInterface.registered.getReply(ConfigBroadcastRegisteredRequest{})); state bool registered = reply.registered; if (self->activeConfigNodes.find(address) != self->activeConfigNodes.end()) { self->activeConfigNodes.erase(address); // Since a node can die and re-register before the broadcaster // receives notice that the node has died, we need to check for // re-registration of a node here. There are two places that can // reset the promise to allow new nodes, make sure the promise is // actually set before resetting it. This prevents a node from // dying, registering, waiting on the promise, then the broadcaster // receives the notification the node has died and resets the // promise again. if (self->newConfigNodesAllowed.isSet()) { self->newConfigNodesAllowed.reset(); } } if (registered) { if (!self->disallowUnregistered) { self->activeConfigNodes.clear(); } self->activeConfigNodes.insert(address); self->disallowUnregistered = true; } else if (self->activeConfigNodes.size() < self->coordinators / 2 + 1 && !self->disallowUnregistered) { // Need to allow registration of previously unregistered nodes when // the cluster first starts up. self->activeConfigNodes.insert(address); if (self->activeConfigNodes.size() >= self->coordinators / 2 + 1 && self->newConfigNodesAllowed.canBeSet()) { self->newConfigNodesAllowed.send(Void()); } } else { self->disallowUnregistered = true; } if (!registered) { wait(self->newConfigNodesAllowed.getFuture()); } wait(success(w.configBroadcastInterface.ready.getReply(ConfigBroadcastReadyRequest{}))); return Void(); } ACTOR static Future<Void> registerNode(ConfigBroadcaster* self, ConfigBroadcasterImpl* impl, WorkerInterface w, Version lastSeenVersion, ConfigClassSet configClassSet, Future<Void> watcher, ConfigBroadcastInterface broadcastInterface) { state BroadcastClientDetails client( watcher, std::move(configClassSet), lastSeenVersion, std::move(broadcastInterface)); if (!impl->consumerFuture.isValid()) { impl->consumerFuture = impl->consumer->consume(*self); } if (impl->clients.count(broadcastInterface.id())) { // Client already registered return Void(); } TraceEvent(SevDebug, "ConfigBroadcasterRegisteringWorker", impl->id) .detail("ClientID", broadcastInterface.id()) .detail("MostRecentVersion", impl->mostRecentVersion); impl->actors.add(registerNodeInternal(impl, w, lastSeenVersion)); // Push full snapshot to worker if it isn't up to date. wait(impl->pushSnapshot(impl->mostRecentVersion, client)); impl->clients[broadcastInterface.id()] = client; impl->clientFailures[broadcastInterface.id()] = waitForFailure(impl, watcher, broadcastInterface.id(), w.address()); return Void(); } public: Future<Void> registerNode(ConfigBroadcaster& self, WorkerInterface const& w, Version lastSeenVersion, ConfigClassSet configClassSet, Future<Void> watcher, ConfigBroadcastInterface const& broadcastInterface) { return registerNode(&self, this, w, lastSeenVersion, configClassSet, watcher, broadcastInterface); } // Updates the broadcasters knowledge of which replicas are fully up to // date, based on data gathered by the consumer. void updateKnownReplicas(std::vector<ConfigFollowerInterface> const& readReplicas) { if (!newConfigNodesAllowed.canBeSet()) { return; } for (const auto& cfi : readReplicas) { this->activeConfigNodes.insert(cfi.address()); } if (activeConfigNodes.size() >= coordinators / 2 + 1) { disallowUnregistered = true; newConfigNodesAllowed.send(Void()); } } void applyChanges(Standalone<VectorRef<VersionedConfigMutationRef>> const& changes, Version mostRecentVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> const& annotations, std::vector<ConfigFollowerInterface> const& readReplicas) { if (mostRecentVersion >= 0) { TraceEvent(SevDebug, "ConfigBroadcasterApplyingChanges", id) .detail("ChangesSize", changes.size()) .detail("CurrentMostRecentVersion", this->mostRecentVersion) .detail("NewMostRecentVersion", mostRecentVersion) .detail("ActiveReplicas", readReplicas.size()); addChanges(changes, mostRecentVersion, annotations); } updateKnownReplicas(readReplicas); } template <class Snapshot> void applySnapshotAndChanges(Snapshot&& snapshot, Version snapshotVersion, Standalone<VectorRef<VersionedConfigMutationRef>> const& changes, Version changesVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> const& annotations, std::vector<ConfigFollowerInterface> const& readReplicas) { TraceEvent(SevDebug, "ConfigBroadcasterApplyingSnapshotAndChanges", id) .detail("CurrentMostRecentVersion", this->mostRecentVersion) .detail("SnapshotSize", snapshot.size()) .detail("SnapshotVersion", snapshotVersion) .detail("ChangesSize", changes.size()) .detail("ChangesVersion", changesVersion) .detail("ActiveReplicas", readReplicas.size()); actors.add(pushSnapshotAndChanges(this, snapshot, snapshotVersion, changes, changesVersion, annotations)); updateKnownReplicas(readReplicas); } ConfigBroadcasterImpl(ConfigFollowerInterface const& cfi) : ConfigBroadcasterImpl() { configDBType = ConfigDBType::SIMPLE; coordinators = 1; consumer = IConfigConsumer::createTestSimple(cfi, 0.5, Optional<double>{}); TraceEvent(SevDebug, "ConfigBroadcasterStartingConsumer", id).detail("Consumer", consumer->getID()); } ConfigBroadcasterImpl(ServerCoordinators const& coordinators, ConfigDBType configDBType) : ConfigBroadcasterImpl() { this->configDBType = configDBType; this->coordinators = coordinators.configServers.size(); if (configDBType != ConfigDBType::DISABLED) { if (configDBType == ConfigDBType::SIMPLE) { consumer = IConfigConsumer::createSimple(coordinators, 0.5, Optional<double>{}); } else { consumer = IConfigConsumer::createPaxos(coordinators, 0.5, Optional<double>{}); } TraceEvent(SevDebug, "ConfigBroadcasterStartingConsumer", id) .detail("Consumer", consumer->getID()) .detail("UsingSimpleConsumer", configDBType == ConfigDBType::SIMPLE); } } JsonBuilderObject getStatus() const { JsonBuilderObject result; JsonBuilderArray mutationsArray; for (const auto& versionedMutation : mutationHistory) { JsonBuilderObject mutationObject; mutationObject["version"] = versionedMutation.version; const auto& mutation = versionedMutation.mutation; mutationObject["config_class"] = mutation.getConfigClass().orDefault("<global>"_sr); mutationObject["knob_name"] = mutation.getKnobName(); mutationObject["knob_value"] = mutation.getValue().toString(); mutationsArray.push_back(std::move(mutationObject)); } result["mutations"] = std::move(mutationsArray); JsonBuilderArray commitsArray; for (const auto& versionedAnnotation : annotationHistory) { JsonBuilderObject commitObject; commitObject["version"] = versionedAnnotation.version; commitObject["description"] = versionedAnnotation.annotation.description; commitObject["timestamp"] = versionedAnnotation.annotation.timestamp; commitsArray.push_back(std::move(commitObject)); } result["commits"] = std::move(commitsArray); JsonBuilderObject snapshotObject; std::map<Optional<Key>, std::vector<std::pair<Key, Value>>> snapshotMap; for (const auto& [configKey, value] : snapshot) { snapshotMap[configKey.configClass.castTo<Key>()].emplace_back(configKey.knobName, value.toString()); } for (const auto& [configClass, kvs] : snapshotMap) { JsonBuilderObject kvsObject; for (const auto& [knobName, knobValue] : kvs) { kvsObject[knobName] = knobValue; } snapshotObject[configClass.orDefault("<global>"_sr)] = std::move(kvsObject); } result["snapshot"] = std::move(snapshotObject); result["last_compacted_version"] = lastCompactedVersion; result["most_recent_version"] = mostRecentVersion; return result; } void compact(Version compactionVersion) { { auto it = std::find_if(mutationHistory.begin(), mutationHistory.end(), [compactionVersion](const auto& vm) { return vm.version > compactionVersion; }); mutationHistory.erase(mutationHistory.begin(), it); } { auto it = std::find_if(annotationHistory.begin(), annotationHistory.end(), [compactionVersion](const auto& va) { return va.version > compactionVersion; }); annotationHistory.erase(annotationHistory.begin(), it); } } Future<Void> getError() const { return consumerFuture || actors.getResult(); } Future<Void> getClientFailure(UID clientUID) const { return clientFailures.find(clientUID)->second; } UID getID() const { return id; } static void runPendingRequestStoreTest(bool includeGlobalMutation, int expectedMatches); }; ConfigBroadcaster::ConfigBroadcaster(ConfigFollowerInterface const& cfi) : impl(PImpl<ConfigBroadcasterImpl>::create(cfi)) {} ConfigBroadcaster::ConfigBroadcaster(ServerCoordinators const& coordinators, ConfigDBType configDBType) : impl(PImpl<ConfigBroadcasterImpl>::create(coordinators, configDBType)) {} ConfigBroadcaster::ConfigBroadcaster(ConfigBroadcaster&&) = default; ConfigBroadcaster& ConfigBroadcaster::operator=(ConfigBroadcaster&&) = default; ConfigBroadcaster::~ConfigBroadcaster() = default; Future<Void> ConfigBroadcaster::registerNode(WorkerInterface const& w, Version lastSeenVersion, ConfigClassSet const& configClassSet, Future<Void> watcher, ConfigBroadcastInterface const& broadcastInterface) { return impl->registerNode(*this, w, lastSeenVersion, configClassSet, watcher, broadcastInterface); } void ConfigBroadcaster::applyChanges(Standalone<VectorRef<VersionedConfigMutationRef>> const& changes, Version mostRecentVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> const& annotations, std::vector<ConfigFollowerInterface> const& readReplicas) { impl->applyChanges(changes, mostRecentVersion, annotations, readReplicas); } void ConfigBroadcaster::applySnapshotAndChanges( std::map<ConfigKey, KnobValue> const& snapshot, Version snapshotVersion, Standalone<VectorRef<VersionedConfigMutationRef>> const& changes, Version changesVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> const& annotations, std::vector<ConfigFollowerInterface> const& readReplicas) { impl->applySnapshotAndChanges(snapshot, snapshotVersion, changes, changesVersion, annotations, readReplicas); } void ConfigBroadcaster::applySnapshotAndChanges( std::map<ConfigKey, KnobValue>&& snapshot, Version snapshotVersion, Standalone<VectorRef<VersionedConfigMutationRef>> const& changes, Version changesVersion, Standalone<VectorRef<VersionedConfigCommitAnnotationRef>> const& annotations, std::vector<ConfigFollowerInterface> const& readReplicas) { impl->applySnapshotAndChanges( std::move(snapshot), snapshotVersion, changes, changesVersion, annotations, readReplicas); } Future<Void> ConfigBroadcaster::getError() const { return impl->getError(); } Future<Void> ConfigBroadcaster::getClientFailure(UID clientUID) const { return impl->getClientFailure(clientUID); } UID ConfigBroadcaster::getID() const { return impl->getID(); } JsonBuilderObject ConfigBroadcaster::getStatus() const { return impl->getStatus(); } void ConfigBroadcaster::compact(Version compactionVersion) { impl->compact(compactionVersion); }