2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.cluster.datastore;
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.collect.Collections2;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableRangeSet;
18 import com.google.common.collect.RangeSet;
19 import com.google.common.primitives.UnsignedLong;
20 import java.util.Collection;
21 import java.util.HashMap;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
25 import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
26 import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
27 import org.opendaylight.controller.cluster.datastore.persisted.FrontendClientMetadata;
28 import org.opendaylight.controller.cluster.datastore.persisted.FrontendHistoryMetadata;
29 import org.opendaylight.controller.cluster.datastore.utils.UnsignedLongRangeSet;
30 import org.opendaylight.yangtools.concepts.Builder;
31 import org.opendaylight.yangtools.concepts.Identifiable;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * This class is NOT thread-safe.
38 abstract class FrontendClientMetadataBuilder implements Builder<FrontendClientMetadata>,
39 Identifiable<ClientIdentifier> {
40 static final class Disabled extends FrontendClientMetadataBuilder {
41 Disabled(final String shardName, final ClientIdentifier identifier) {
42 super(shardName, identifier);
46 public FrontendClientMetadata build() {
47 return new FrontendClientMetadata(getIdentifier(), ImmutableRangeSet.of(), ImmutableList.of());
51 void onHistoryCreated(final LocalHistoryIdentifier historyId) {
56 void onHistoryClosed(final LocalHistoryIdentifier historyId) {
61 void onHistoryPurged(final LocalHistoryIdentifier historyId) {
66 void onTransactionAborted(final TransactionIdentifier txId) {
71 void onTransactionCommitted(final TransactionIdentifier txId) {
76 void onTransactionPurged(final TransactionIdentifier txId) {
81 LeaderFrontendState toLeaderState(final Shard shard) {
82 return new LeaderFrontendState.Disabled(shard.persistenceId(), getIdentifier(), shard.getDataStore());
86 static final class Enabled extends FrontendClientMetadataBuilder {
88 private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
89 private final UnsignedLongRangeSet purgedHistories;
90 private final LocalHistoryIdentifier standaloneId;
92 Enabled(final String shardName, final ClientIdentifier identifier) {
93 super(shardName, identifier);
95 purgedHistories = UnsignedLongRangeSet.create();
97 // History for stand-alone transactions is always present
98 standaloneId = standaloneHistoryId();
99 currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
102 Enabled(final String shardName, final FrontendClientMetadata meta) {
103 super(shardName, meta.getIdentifier());
105 purgedHistories = UnsignedLongRangeSet.create(meta.getPurgedHistories());
106 for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
107 final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(getIdentifier(), h);
108 currentHistories.put(b.getIdentifier(), b);
111 // Sanity check and recovery
112 standaloneId = standaloneHistoryId();
113 if (!currentHistories.containsKey(standaloneId)) {
114 LOG.warn("{}: Client {} recovered histories {} do not contain stand-alone history, attempting recovery",
115 shardName, getIdentifier(), currentHistories);
116 currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
121 public FrontendClientMetadata build() {
122 return new FrontendClientMetadata(getIdentifier(), purgedHistories.toImmutable(),
123 Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
127 void onHistoryCreated(final LocalHistoryIdentifier historyId) {
128 final FrontendHistoryMetadataBuilder newMeta = new FrontendHistoryMetadataBuilder(historyId);
129 final FrontendHistoryMetadataBuilder oldMeta = currentHistories.putIfAbsent(historyId, newMeta);
130 if (oldMeta != null) {
131 // This should not be happening, warn about it
132 LOG.warn("{}: Reused local history {}", shardName(), historyId);
134 LOG.debug("{}: Created local history {}", shardName(), historyId);
139 void onHistoryClosed(final LocalHistoryIdentifier historyId) {
140 final FrontendHistoryMetadataBuilder builder = currentHistories.get(historyId);
141 if (builder != null) {
142 builder.onHistoryClosed();
143 LOG.debug("{}: Closed history {}", shardName(), historyId);
145 LOG.warn("{}: Closed unknown history {}, ignoring", shardName(), historyId);
150 void onHistoryPurged(final LocalHistoryIdentifier historyId) {
151 final FrontendHistoryMetadataBuilder history = currentHistories.remove(historyId);
152 final long historyBits = historyId.getHistoryId();
153 if (history == null) {
154 if (!purgedHistories.contains(historyBits)) {
155 purgedHistories.add(historyBits);
156 LOG.warn("{}: Purging unknown history {}", shardName(), historyId);
158 LOG.warn("{}: Duplicate purge of history {}", shardName(), historyId);
161 purgedHistories.add(historyBits);
162 LOG.debug("{}: Purged history {}", shardName(), historyId);
167 void onTransactionAborted(final TransactionIdentifier txId) {
168 final FrontendHistoryMetadataBuilder history = getHistory(txId);
169 if (history != null) {
170 history.onTransactionAborted(txId);
171 LOG.debug("{}: Aborted transaction {}", shardName(), txId);
173 LOG.warn("{}: Unknown history for aborted transaction {}, ignoring", shardName(), txId);
178 void onTransactionCommitted(final TransactionIdentifier txId) {
179 final FrontendHistoryMetadataBuilder history = getHistory(txId);
180 if (history != null) {
181 history.onTransactionCommitted(txId);
182 LOG.debug("{}: Committed transaction {}", shardName(), txId);
184 LOG.warn("{}: Unknown history for commited transaction {}, ignoring", shardName(), txId);
189 void onTransactionPurged(final TransactionIdentifier txId) {
190 final FrontendHistoryMetadataBuilder history = getHistory(txId);
191 if (history != null) {
192 history.onTransactionPurged(txId);
193 LOG.debug("{}: Purged transaction {}", shardName(), txId);
195 LOG.warn("{}: Unknown history for purged transaction {}, ignoring", shardName(), txId);
200 LeaderFrontendState toLeaderState(final Shard shard) {
201 // Note: we have to make sure to *copy* all current state and not leak any views, otherwise leader/follower
202 // interactions would get intertwined leading to inconsistencies.
203 final Map<LocalHistoryIdentifier, LocalFrontendHistory> histories = new HashMap<>();
204 for (FrontendHistoryMetadataBuilder e : currentHistories.values()) {
205 if (e.getIdentifier().getHistoryId() != 0) {
206 final AbstractFrontendHistory state = e.toLeaderState(shard);
207 verify(state instanceof LocalFrontendHistory, "Unexpected state %s", state);
208 histories.put(e.getIdentifier(), (LocalFrontendHistory) state);
212 final AbstractFrontendHistory singleHistory;
213 final FrontendHistoryMetadataBuilder singleHistoryMeta = currentHistories.get(
214 new LocalHistoryIdentifier(getIdentifier(), 0));
215 if (singleHistoryMeta == null) {
216 final ShardDataTree tree = shard.getDataStore();
217 singleHistory = StandaloneFrontendHistory.create(shard.persistenceId(), getIdentifier(), tree);
219 singleHistory = singleHistoryMeta.toLeaderState(shard);
222 return new LeaderFrontendState.Enabled(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
223 purgedHistories.copy(), singleHistory, histories);
227 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
228 return super.addToStringAttributes(helper).add("current", currentHistories).add("purged", purgedHistories);
231 private FrontendHistoryMetadataBuilder getHistory(final TransactionIdentifier txId) {
232 LocalHistoryIdentifier historyId = txId.getHistoryId();
233 if (historyId.getHistoryId() == 0 && historyId.getCookie() != 0) {
234 // We are pre-creating the history for free-standing transactions with a zero cookie, hence our lookup
235 // needs to account for that.
236 LOG.debug("{}: looking up {} instead of {}", shardName(), standaloneId, historyId);
237 historyId = standaloneId;
240 return currentHistories.get(historyId);
243 private LocalHistoryIdentifier standaloneHistoryId() {
244 return new LocalHistoryIdentifier(getIdentifier(), 0);
248 private static final Logger LOG = LoggerFactory.getLogger(FrontendClientMetadataBuilder.class);
250 private final ClientIdentifier identifier;
251 private final String shardName;
253 FrontendClientMetadataBuilder(final String shardName, final ClientIdentifier identifier) {
254 this.shardName = requireNonNull(shardName);
255 this.identifier = requireNonNull(identifier);
258 static FrontendClientMetadataBuilder of(final String shardName, final FrontendClientMetadata meta) {
259 final Collection<FrontendHistoryMetadata> current = meta.getCurrentHistories();
260 final RangeSet<UnsignedLong> purged = meta.getPurgedHistories();
262 // Completely empty histories imply disabled state, as otherwise we'd have a record of the single history --
263 // either purged or active
264 return current.isEmpty() && purged.isEmpty() ? new Disabled(shardName, meta.getIdentifier())
265 : new Enabled(shardName, meta);
269 public final ClientIdentifier getIdentifier() {
273 final String shardName() {
277 abstract void onHistoryCreated(LocalHistoryIdentifier historyId);
279 abstract void onHistoryClosed(LocalHistoryIdentifier historyId);
281 abstract void onHistoryPurged(LocalHistoryIdentifier historyId);
283 abstract void onTransactionAborted(TransactionIdentifier txId);
285 abstract void onTransactionCommitted(TransactionIdentifier txId);
287 abstract void onTransactionPurged(TransactionIdentifier txId);
290 * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
292 * @param shard parent shard
293 * @return Leader frontend state
295 abstract @NonNull LeaderFrontendState toLeaderState(@NonNull Shard shard);
298 public final String toString() {
299 return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
302 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
303 return helper.add("identifier", identifier);