fbaf76fbc5098b6ea2ca07929de5acb00ed9e587
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / datastore / FrontendClientMetadataBuilder.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.cluster.datastore;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.collect.Collections2;
15 import java.util.HashMap;
16 import java.util.Map;
17 import org.eclipse.jdt.annotation.NonNull;
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;
28
29 /**
30  * This class is NOT thread-safe.
31  */
32 final class FrontendClientMetadataBuilder implements Builder<FrontendClientMetadata>, Identifiable<ClientIdentifier> {
33     private static final Logger LOG = LoggerFactory.getLogger(FrontendClientMetadataBuilder.class);
34
35     private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
36     private final UnsignedLongRangeSet purgedHistories;
37     private final LocalHistoryIdentifier standaloneId;
38     private final ClientIdentifier identifier;
39     private final String shardName;
40
41     FrontendClientMetadataBuilder(final String shardName, final ClientIdentifier identifier) {
42         this.shardName = requireNonNull(shardName);
43         this.identifier = requireNonNull(identifier);
44         purgedHistories = UnsignedLongRangeSet.create();
45
46         // History for stand-alone transactions is always present
47         standaloneId = standaloneHistoryId();
48         currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
49     }
50
51     FrontendClientMetadataBuilder(final String shardName, final FrontendClientMetadata meta) {
52         this.shardName = requireNonNull(shardName);
53         this.identifier = meta.getIdentifier();
54         purgedHistories = UnsignedLongRangeSet.create(meta.getPurgedHistories());
55
56         for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
57             final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(identifier, h);
58             currentHistories.put(b.getIdentifier(), b);
59         }
60
61         // Sanity check and recovery
62         standaloneId = standaloneHistoryId();
63         if (!currentHistories.containsKey(standaloneId)) {
64             LOG.warn("{}: Client {} recovered histories {} do not contain stand-alone history, attempting recovery",
65                 shardName, identifier, currentHistories);
66             currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
67         }
68     }
69
70     private LocalHistoryIdentifier standaloneHistoryId() {
71         return new LocalHistoryIdentifier(identifier, 0);
72     }
73
74     @Override
75     public FrontendClientMetadata build() {
76         return new FrontendClientMetadata(identifier, purgedHistories.toImmutable(),
77             Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
78     }
79
80     @Override
81     public ClientIdentifier getIdentifier() {
82         return identifier;
83     }
84
85     void onHistoryCreated(final LocalHistoryIdentifier historyId) {
86         final FrontendHistoryMetadataBuilder newMeta = new FrontendHistoryMetadataBuilder(historyId);
87         final FrontendHistoryMetadataBuilder oldMeta = currentHistories.putIfAbsent(historyId, newMeta);
88         if (oldMeta != null) {
89             // This should not be happening, warn about it
90             LOG.warn("{}: Reused local history {}", shardName, historyId);
91         } else {
92             LOG.debug("{}: Created local history {}", shardName, historyId);
93         }
94     }
95
96     void onHistoryClosed(final LocalHistoryIdentifier historyId) {
97         final FrontendHistoryMetadataBuilder builder = currentHistories.get(historyId);
98         if (builder != null) {
99             builder.onHistoryClosed();
100             LOG.debug("{}: Closed history {}", shardName, historyId);
101         } else {
102             LOG.warn("{}: Closed unknown history {}, ignoring", shardName, historyId);
103         }
104     }
105
106     void onHistoryPurged(final LocalHistoryIdentifier historyId) {
107         final FrontendHistoryMetadataBuilder history = currentHistories.remove(historyId);
108         final long historyBits = historyId.getHistoryId();
109         if (history == null) {
110             if (!purgedHistories.contains(historyBits)) {
111                 purgedHistories.add(historyBits);
112                 LOG.warn("{}: Purging unknown history {}", shardName, historyId);
113             } else {
114                 LOG.warn("{}: Duplicate purge of history {}", shardName, historyId);
115             }
116         } else {
117             purgedHistories.add(historyBits);
118             LOG.debug("{}: Purged history {}", shardName, historyId);
119         }
120     }
121
122     void onTransactionAborted(final TransactionIdentifier txId) {
123         final FrontendHistoryMetadataBuilder history = getHistory(txId);
124         if (history != null) {
125             history.onTransactionAborted(txId);
126             LOG.debug("{}: Aborted transaction {}", shardName, txId);
127         } else {
128             LOG.warn("{}: Unknown history for aborted transaction {}, ignoring", shardName, txId);
129         }
130     }
131
132     void onTransactionCommitted(final TransactionIdentifier txId) {
133         final FrontendHistoryMetadataBuilder history = getHistory(txId);
134         if (history != null) {
135             history.onTransactionCommitted(txId);
136             LOG.debug("{}: Committed transaction {}", shardName, txId);
137         } else {
138             LOG.warn("{}: Unknown history for commited transaction {}, ignoring", shardName, txId);
139         }
140     }
141
142     void onTransactionPurged(final TransactionIdentifier txId) {
143         final FrontendHistoryMetadataBuilder history = getHistory(txId);
144         if (history != null) {
145             history.onTransactionPurged(txId);
146             LOG.debug("{}: Purged transaction {}", shardName, txId);
147         } else {
148             LOG.warn("{}: Unknown history for purged transaction {}, ignoring", shardName, txId);
149         }
150     }
151
152     /**
153      * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
154      *
155      * @param shard parent shard
156      * @return Leader frontend state
157      */
158     @NonNull LeaderFrontendState toLeaderState(final @NonNull Shard shard) {
159         // Note: we have to make sure to *copy* all current state and not leak any views, otherwise leader/follower
160         //       interactions would get intertwined leading to inconsistencies.
161         final Map<LocalHistoryIdentifier, LocalFrontendHistory> histories = new HashMap<>();
162         for (FrontendHistoryMetadataBuilder e : currentHistories.values()) {
163             if (e.getIdentifier().getHistoryId() != 0) {
164                 final AbstractFrontendHistory state = e.toLeaderState(shard);
165                 verify(state instanceof LocalFrontendHistory, "Unexpected state %s", state);
166                 histories.put(e.getIdentifier(), (LocalFrontendHistory) state);
167             }
168         }
169
170         final AbstractFrontendHistory singleHistory;
171         final FrontendHistoryMetadataBuilder singleHistoryMeta = currentHistories.get(
172             new LocalHistoryIdentifier(identifier, 0));
173         if (singleHistoryMeta == null) {
174             final ShardDataTree tree = shard.getDataStore();
175             singleHistory = StandaloneFrontendHistory.create(shard.persistenceId(), getIdentifier(), tree);
176         } else {
177             singleHistory = singleHistoryMeta.toLeaderState(shard);
178         }
179
180         return new LeaderFrontendState(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
181             purgedHistories.copy(), singleHistory, histories);
182     }
183
184     private FrontendHistoryMetadataBuilder getHistory(final TransactionIdentifier txId) {
185         LocalHistoryIdentifier historyId = txId.getHistoryId();
186         if (historyId.getHistoryId() == 0 && historyId.getCookie() != 0) {
187             // We are pre-creating the history for free-standing transactions with a zero cookie, hence our lookup
188             // needs to account for that.
189             LOG.debug("{}: looking up {} instead of {}", shardName, standaloneId, historyId);
190             historyId = standaloneId;
191         }
192
193         return currentHistories.get(historyId);
194     }
195
196     @Override
197     public String toString() {
198         return MoreObjects.toStringHelper(this).add("identifier", identifier).add("current", currentHistories)
199                 .add("purged", purgedHistories).toString();
200     }
201 }