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 com.google.common.base.MoreObjects;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Verify;
13 import com.google.common.collect.Collections2;
14 import java.util.HashMap;
16 import javax.annotation.Nonnull;
17 import javax.annotation.concurrent.NotThreadSafe;
18 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
19 import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
20 import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
21 import org.opendaylight.controller.cluster.datastore.persisted.FrontendClientMetadata;
22 import org.opendaylight.controller.cluster.datastore.persisted.FrontendHistoryMetadata;
23 import org.opendaylight.controller.cluster.datastore.utils.UnsignedLongRangeSet;
24 import org.opendaylight.yangtools.concepts.Builder;
25 import org.opendaylight.yangtools.concepts.Identifiable;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 final class FrontendClientMetadataBuilder implements Builder<FrontendClientMetadata>, Identifiable<ClientIdentifier> {
31 private static final Logger LOG = LoggerFactory.getLogger(FrontendClientMetadataBuilder.class);
33 private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
34 private final UnsignedLongRangeSet purgedHistories;
35 private final ClientIdentifier identifier;
36 private final String shardName;
38 FrontendClientMetadataBuilder(final String shardName, final ClientIdentifier identifier) {
39 this.shardName = Preconditions.checkNotNull(shardName);
40 this.identifier = Preconditions.checkNotNull(identifier);
41 purgedHistories = UnsignedLongRangeSet.create();
43 // History for stand-alone transactions is always present
44 final LocalHistoryIdentifier standaloneId = standaloneHistoryId();
45 currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
48 FrontendClientMetadataBuilder(final String shardName, final FrontendClientMetadata meta) {
49 this.shardName = Preconditions.checkNotNull(shardName);
50 this.identifier = Preconditions.checkNotNull(meta.getIdentifier());
51 purgedHistories = UnsignedLongRangeSet.create(meta.getPurgedHistories());
53 for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
54 final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(identifier, h);
55 currentHistories.put(b.getIdentifier(), b);
58 // Sanity check and recovery
59 final LocalHistoryIdentifier standaloneId = standaloneHistoryId();
60 if (!currentHistories.containsKey(standaloneId)) {
61 LOG.warn("{}: Client {} recovered histories {} do not contain stand-alone history, attempting recovery",
62 shardName, identifier, currentHistories);
63 currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
67 private LocalHistoryIdentifier standaloneHistoryId() {
68 return new LocalHistoryIdentifier(identifier, 0);
72 public FrontendClientMetadata build() {
73 return new FrontendClientMetadata(identifier, purgedHistories.toImmutable(),
74 Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
78 public ClientIdentifier getIdentifier() {
82 void onHistoryCreated(final LocalHistoryIdentifier historyId) {
83 final FrontendHistoryMetadataBuilder newMeta = new FrontendHistoryMetadataBuilder(historyId);
84 final FrontendHistoryMetadataBuilder oldMeta = currentHistories.putIfAbsent(historyId, newMeta);
85 if (oldMeta != null) {
86 // This should not be happening, warn about it
87 LOG.warn("{}: Reused local history {}", shardName, historyId);
89 LOG.debug("{}: Created local history {}", shardName, historyId);
93 void onHistoryClosed(final LocalHistoryIdentifier historyId) {
94 final FrontendHistoryMetadataBuilder builder = currentHistories.get(historyId);
95 if (builder != null) {
96 builder.onHistoryClosed();
97 LOG.debug("{}: Closed history {}", shardName, historyId);
99 LOG.warn("{}: Closed unknown history {}, ignoring", shardName, historyId);
103 void onHistoryPurged(final LocalHistoryIdentifier historyId) {
104 final FrontendHistoryMetadataBuilder history = currentHistories.remove(historyId);
105 if (history == null) {
106 LOG.warn("{}: Purging unknown history {}", shardName, historyId);
109 // XXX: do we need to account for cookies?
110 purgedHistories.add(historyId.getHistoryId());
111 LOG.debug("{}: Purged history {}", historyId);
114 void onTransactionAborted(final TransactionIdentifier txId) {
115 final FrontendHistoryMetadataBuilder history = getHistory(txId);
116 if (history != null) {
117 history.onTransactionAborted(txId);
118 LOG.debug("{}: Aborted transaction {}", shardName, txId);
120 LOG.warn("{}: Unknown history for aborted transaction {}, ignoring", shardName, txId);
124 void onTransactionCommitted(final TransactionIdentifier txId) {
125 final FrontendHistoryMetadataBuilder history = getHistory(txId);
126 if (history != null) {
127 history.onTransactionCommitted(txId);
128 LOG.debug("{}: Committed transaction {}", shardName, txId);
130 LOG.warn("{}: Unknown history for commited transaction {}, ignoring", shardName, txId);
134 void onTransactionPurged(final TransactionIdentifier txId) {
135 final FrontendHistoryMetadataBuilder history = getHistory(txId);
136 if (history != null) {
137 history.onTransactionPurged(txId);
138 LOG.debug("{}: Purged transaction {}", shardName, txId);
140 LOG.warn("{}: Unknown history for purged transaction {}, ignoring", shardName, txId);
145 * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
147 * @param shard parent shard
148 * @return Leader frontend state
150 @Nonnull LeaderFrontendState toLeaderState(@Nonnull final Shard shard) {
151 // Note: we have to make sure to *copy* all current state and not leak any views, otherwise leader/follower
152 // interactions would get intertwined leading to inconsistencies.
153 final Map<LocalHistoryIdentifier, LocalFrontendHistory> histories = new HashMap<>();
154 for (FrontendHistoryMetadataBuilder e : currentHistories.values()) {
155 if (e.getIdentifier().getHistoryId() != 0) {
156 final AbstractFrontendHistory state = e.toLeaderState(shard);
157 Verify.verify(state instanceof LocalFrontendHistory);
158 histories.put(e.getIdentifier(), (LocalFrontendHistory) state);
162 final AbstractFrontendHistory singleHistory;
163 final FrontendHistoryMetadataBuilder singleHistoryMeta = currentHistories.get(
164 new LocalHistoryIdentifier(identifier, 0));
165 if (singleHistoryMeta == null) {
166 final ShardDataTree tree = shard.getDataStore();
167 singleHistory = StandaloneFrontendHistory.create(shard.persistenceId(), getIdentifier(), tree);
169 singleHistory = singleHistoryMeta.toLeaderState(shard);
172 return new LeaderFrontendState(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
173 purgedHistories.copy(), singleHistory, histories);
176 private FrontendHistoryMetadataBuilder getHistory(final TransactionIdentifier txId) {
177 return currentHistories.get(txId.getHistoryId());
181 public String toString() {
182 return MoreObjects.toStringHelper(this).add("identifier", identifier).add("current", currentHistories)
183 .add("purged", purgedHistories).toString();