Raise EOS unsuccessful request reporting to error
[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.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;
22 import java.util.Map;
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;
34
35 /**
36  * This class is NOT thread-safe.
37  */
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);
43         }
44
45         @Override
46         public FrontendClientMetadata build() {
47             return new FrontendClientMetadata(getIdentifier(), ImmutableRangeSet.of(), ImmutableList.of());
48         }
49
50         @Override
51         void onHistoryCreated(final LocalHistoryIdentifier historyId) {
52             // No-op
53         }
54
55         @Override
56         void onHistoryClosed(final LocalHistoryIdentifier historyId) {
57             // No-op
58         }
59
60         @Override
61         void onHistoryPurged(final LocalHistoryIdentifier historyId) {
62             // No-op
63         }
64
65         @Override
66         void onTransactionAborted(final TransactionIdentifier txId) {
67             // No-op
68         }
69
70         @Override
71         void onTransactionCommitted(final TransactionIdentifier txId) {
72             // No-op
73         }
74
75         @Override
76         void onTransactionPurged(final TransactionIdentifier txId) {
77             // No-op
78         }
79
80         @Override
81         LeaderFrontendState toLeaderState(final Shard shard) {
82             return new LeaderFrontendState.Disabled(shard.persistenceId(), getIdentifier(), shard.getDataStore());
83         }
84     }
85
86     static final class Enabled extends FrontendClientMetadataBuilder {
87
88         private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
89         private final UnsignedLongRangeSet purgedHistories;
90         private final LocalHistoryIdentifier standaloneId;
91
92         Enabled(final String shardName, final ClientIdentifier identifier) {
93             super(shardName, identifier);
94
95             purgedHistories = UnsignedLongRangeSet.create();
96
97             // History for stand-alone transactions is always present
98             standaloneId = standaloneHistoryId();
99             currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
100         }
101
102         Enabled(final String shardName, final FrontendClientMetadata meta) {
103             super(shardName, meta.getIdentifier());
104
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);
109             }
110
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));
117             }
118         }
119
120         @Override
121         public FrontendClientMetadata build() {
122             return new FrontendClientMetadata(getIdentifier(), purgedHistories.toImmutable(),
123                 Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
124         }
125
126         @Override
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);
133             } else {
134                 LOG.debug("{}: Created local history {}", shardName(), historyId);
135             }
136         }
137
138         @Override
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);
144             } else {
145                 LOG.warn("{}: Closed unknown history {}, ignoring", shardName(), historyId);
146             }
147         }
148
149         @Override
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);
157                 } else {
158                     LOG.warn("{}: Duplicate purge of history {}", shardName(), historyId);
159                 }
160             } else {
161                 purgedHistories.add(historyBits);
162                 LOG.debug("{}: Purged history {}", shardName(), historyId);
163             }
164         }
165
166         @Override
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);
172             } else {
173                 LOG.warn("{}: Unknown history for aborted transaction {}, ignoring", shardName(), txId);
174             }
175         }
176
177         @Override
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);
183             } else {
184                 LOG.warn("{}: Unknown history for commited transaction {}, ignoring", shardName(), txId);
185             }
186         }
187
188         @Override
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);
194             } else {
195                 LOG.warn("{}: Unknown history for purged transaction {}, ignoring", shardName(), txId);
196             }
197         }
198
199         @Override
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);
209                 }
210             }
211
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);
218             } else {
219                 singleHistory = singleHistoryMeta.toLeaderState(shard);
220             }
221
222             return new LeaderFrontendState.Enabled(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
223                 purgedHistories.copy(), singleHistory, histories);
224         }
225
226         @Override
227         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
228             return super.addToStringAttributes(helper).add("current", currentHistories).add("purged", purgedHistories);
229         }
230
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;
238             }
239
240             return currentHistories.get(historyId);
241         }
242
243         private LocalHistoryIdentifier standaloneHistoryId() {
244             return new LocalHistoryIdentifier(getIdentifier(), 0);
245         }
246     }
247
248     private static final Logger LOG = LoggerFactory.getLogger(FrontendClientMetadataBuilder.class);
249
250     private final ClientIdentifier identifier;
251     private final String shardName;
252
253     FrontendClientMetadataBuilder(final String shardName, final ClientIdentifier identifier) {
254         this.shardName = requireNonNull(shardName);
255         this.identifier = requireNonNull(identifier);
256     }
257
258     static FrontendClientMetadataBuilder of(final String shardName, final FrontendClientMetadata meta) {
259         final Collection<FrontendHistoryMetadata> current = meta.getCurrentHistories();
260         final RangeSet<UnsignedLong> purged = meta.getPurgedHistories();
261
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);
266     }
267
268     @Override
269     public final ClientIdentifier getIdentifier() {
270         return identifier;
271     }
272
273     final String shardName() {
274         return shardName;
275     }
276
277     abstract void onHistoryCreated(LocalHistoryIdentifier historyId);
278
279     abstract void onHistoryClosed(LocalHistoryIdentifier historyId);
280
281     abstract void onHistoryPurged(LocalHistoryIdentifier historyId);
282
283     abstract void onTransactionAborted(TransactionIdentifier txId);
284
285     abstract void onTransactionCommitted(TransactionIdentifier txId);
286
287     abstract void onTransactionPurged(TransactionIdentifier txId);
288
289     /**
290      * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
291      *
292      * @param shard parent shard
293      * @return Leader frontend state
294      */
295     abstract @NonNull LeaderFrontendState toLeaderState(@NonNull Shard shard);
296
297     @Override
298     public final String toString() {
299         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
300     }
301
302     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
303         return helper.add("identifier", identifier);
304     }
305 }