Use UnsignedLongSet instead of RangeSet in metadata
[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 java.util.HashMap;
18 import java.util.Map;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
21 import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
22 import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
23 import org.opendaylight.controller.cluster.datastore.persisted.FrontendClientMetadata;
24 import org.opendaylight.controller.cluster.datastore.persisted.FrontendHistoryMetadata;
25 import org.opendaylight.controller.cluster.datastore.utils.ImmutableUnsignedLongSet;
26 import org.opendaylight.controller.cluster.datastore.utils.MutableUnsignedLongSet;
27 import org.opendaylight.yangtools.concepts.Builder;
28 import org.opendaylight.yangtools.concepts.Identifiable;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * This class is NOT thread-safe.
34  */
35 abstract class FrontendClientMetadataBuilder implements Builder<FrontendClientMetadata>,
36         Identifiable<ClientIdentifier> {
37     static final class Disabled extends FrontendClientMetadataBuilder {
38         Disabled(final String shardName, final ClientIdentifier identifier) {
39             super(shardName, identifier);
40         }
41
42         @Override
43         public FrontendClientMetadata build() {
44             return new FrontendClientMetadata(getIdentifier(), ImmutableUnsignedLongSet.of(), ImmutableList.of());
45         }
46
47         @Override
48         void onHistoryCreated(final LocalHistoryIdentifier historyId) {
49             // No-op
50         }
51
52         @Override
53         void onHistoryClosed(final LocalHistoryIdentifier historyId) {
54             // No-op
55         }
56
57         @Override
58         void onHistoryPurged(final LocalHistoryIdentifier historyId) {
59             // No-op
60         }
61
62         @Override
63         void onTransactionAborted(final TransactionIdentifier txId) {
64             // No-op
65         }
66
67         @Override
68         void onTransactionCommitted(final TransactionIdentifier txId) {
69             // No-op
70         }
71
72         @Override
73         void onTransactionPurged(final TransactionIdentifier txId) {
74             // No-op
75         }
76
77         @Override
78         LeaderFrontendState toLeaderState(final Shard shard) {
79             return new LeaderFrontendState.Disabled(shard.persistenceId(), getIdentifier(), shard.getDataStore());
80         }
81     }
82
83     static final class Enabled extends FrontendClientMetadataBuilder {
84         private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
85         private final MutableUnsignedLongSet purgedHistories;
86         private final LocalHistoryIdentifier standaloneId;
87
88         Enabled(final String shardName, final ClientIdentifier identifier) {
89             super(shardName, identifier);
90
91             purgedHistories = MutableUnsignedLongSet.of();
92
93             // History for stand-alone transactions is always present
94             standaloneId = standaloneHistoryId();
95             currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
96         }
97
98         Enabled(final String shardName, final FrontendClientMetadata meta) {
99             super(shardName, meta.getIdentifier());
100
101             purgedHistories = meta.getPurgedHistories().mutableCopy();
102             for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
103                 final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(getIdentifier(), h);
104                 currentHistories.put(b.getIdentifier(), b);
105             }
106
107             // Sanity check and recovery
108             standaloneId = standaloneHistoryId();
109             if (!currentHistories.containsKey(standaloneId)) {
110                 LOG.warn("{}: Client {} recovered histories {} do not contain stand-alone history, attempting recovery",
111                     shardName, getIdentifier(), currentHistories);
112                 currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
113             }
114         }
115
116         @Override
117         public FrontendClientMetadata build() {
118             return new FrontendClientMetadata(getIdentifier(), purgedHistories.immutableCopy(),
119                 Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
120         }
121
122         @Override
123         void onHistoryCreated(final LocalHistoryIdentifier historyId) {
124             final FrontendHistoryMetadataBuilder newMeta = new FrontendHistoryMetadataBuilder(historyId);
125             final FrontendHistoryMetadataBuilder oldMeta = currentHistories.putIfAbsent(historyId, newMeta);
126             if (oldMeta != null) {
127                 // This should not be happening, warn about it
128                 LOG.warn("{}: Reused local history {}", shardName(), historyId);
129             } else {
130                 LOG.debug("{}: Created local history {}", shardName(), historyId);
131             }
132         }
133
134         @Override
135         void onHistoryClosed(final LocalHistoryIdentifier historyId) {
136             final FrontendHistoryMetadataBuilder builder = currentHistories.get(historyId);
137             if (builder != null) {
138                 builder.onHistoryClosed();
139                 LOG.debug("{}: Closed history {}", shardName(), historyId);
140             } else {
141                 LOG.warn("{}: Closed unknown history {}, ignoring", shardName(), historyId);
142             }
143         }
144
145         @Override
146         void onHistoryPurged(final LocalHistoryIdentifier historyId) {
147             final FrontendHistoryMetadataBuilder history = currentHistories.remove(historyId);
148             final long historyBits = historyId.getHistoryId();
149             if (history == null) {
150                 if (!purgedHistories.contains(historyBits)) {
151                     purgedHistories.add(historyBits);
152                     LOG.warn("{}: Purging unknown history {}", shardName(), historyId);
153                 } else {
154                     LOG.warn("{}: Duplicate purge of history {}", shardName(), historyId);
155                 }
156             } else {
157                 purgedHistories.add(historyBits);
158                 LOG.debug("{}: Purged history {}", shardName(), historyId);
159             }
160         }
161
162         @Override
163         void onTransactionAborted(final TransactionIdentifier txId) {
164             final FrontendHistoryMetadataBuilder history = getHistory(txId);
165             if (history != null) {
166                 history.onTransactionAborted(txId);
167                 LOG.debug("{}: Aborted transaction {}", shardName(), txId);
168             } else {
169                 LOG.warn("{}: Unknown history for aborted transaction {}, ignoring", shardName(), txId);
170             }
171         }
172
173         @Override
174         void onTransactionCommitted(final TransactionIdentifier txId) {
175             final FrontendHistoryMetadataBuilder history = getHistory(txId);
176             if (history != null) {
177                 history.onTransactionCommitted(txId);
178                 LOG.debug("{}: Committed transaction {}", shardName(), txId);
179             } else {
180                 LOG.warn("{}: Unknown history for commited transaction {}, ignoring", shardName(), txId);
181             }
182         }
183
184         @Override
185         void onTransactionPurged(final TransactionIdentifier txId) {
186             final FrontendHistoryMetadataBuilder history = getHistory(txId);
187             if (history != null) {
188                 history.onTransactionPurged(txId);
189                 LOG.debug("{}: Purged transaction {}", shardName(), txId);
190             } else {
191                 LOG.warn("{}: Unknown history for purged transaction {}, ignoring", shardName(), txId);
192             }
193         }
194
195         @Override
196         LeaderFrontendState toLeaderState(final Shard shard) {
197             // Note: we have to make sure to *copy* all current state and not leak any views, otherwise leader/follower
198             //       interactions would get intertwined leading to inconsistencies.
199             final Map<LocalHistoryIdentifier, LocalFrontendHistory> histories = new HashMap<>();
200             for (FrontendHistoryMetadataBuilder e : currentHistories.values()) {
201                 if (e.getIdentifier().getHistoryId() != 0) {
202                     final AbstractFrontendHistory state = e.toLeaderState(shard);
203                     verify(state instanceof LocalFrontendHistory, "Unexpected state %s", state);
204                     histories.put(e.getIdentifier(), (LocalFrontendHistory) state);
205                 }
206             }
207
208             final AbstractFrontendHistory singleHistory;
209             final FrontendHistoryMetadataBuilder singleHistoryMeta = currentHistories.get(
210                 new LocalHistoryIdentifier(getIdentifier(), 0));
211             if (singleHistoryMeta == null) {
212                 final ShardDataTree tree = shard.getDataStore();
213                 singleHistory = StandaloneFrontendHistory.create(shard.persistenceId(), getIdentifier(), tree);
214             } else {
215                 singleHistory = singleHistoryMeta.toLeaderState(shard);
216             }
217
218             return new LeaderFrontendState.Enabled(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
219                 purgedHistories.mutableCopy(), singleHistory, histories);
220         }
221
222         @Override
223         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
224             return super.addToStringAttributes(helper).add("current", currentHistories).add("purged", purgedHistories);
225         }
226
227         private FrontendHistoryMetadataBuilder getHistory(final TransactionIdentifier txId) {
228             LocalHistoryIdentifier historyId = txId.getHistoryId();
229             if (historyId.getHistoryId() == 0 && historyId.getCookie() != 0) {
230                 // We are pre-creating the history for free-standing transactions with a zero cookie, hence our lookup
231                 // needs to account for that.
232                 LOG.debug("{}: looking up {} instead of {}", shardName(), standaloneId, historyId);
233                 historyId = standaloneId;
234             }
235
236             return currentHistories.get(historyId);
237         }
238
239         private LocalHistoryIdentifier standaloneHistoryId() {
240             return new LocalHistoryIdentifier(getIdentifier(), 0);
241         }
242     }
243
244     private static final Logger LOG = LoggerFactory.getLogger(FrontendClientMetadataBuilder.class);
245
246     private final @NonNull ClientIdentifier identifier;
247     private final @NonNull String shardName;
248
249     FrontendClientMetadataBuilder(final String shardName, final ClientIdentifier identifier) {
250         this.shardName = requireNonNull(shardName);
251         this.identifier = requireNonNull(identifier);
252     }
253
254     static FrontendClientMetadataBuilder of(final String shardName, final FrontendClientMetadata meta) {
255         // Completely empty histories imply disabled state, as otherwise we'd have a record of the single history --
256         // either purged or active
257         return  meta.getCurrentHistories().isEmpty() && meta.getPurgedHistories().isEmpty()
258             ? new Disabled(shardName, meta.getIdentifier()) : new Enabled(shardName, meta);
259     }
260
261     @Override
262     public final ClientIdentifier getIdentifier() {
263         return identifier;
264     }
265
266     final String shardName() {
267         return shardName;
268     }
269
270     abstract void onHistoryCreated(LocalHistoryIdentifier historyId);
271
272     abstract void onHistoryClosed(LocalHistoryIdentifier historyId);
273
274     abstract void onHistoryPurged(LocalHistoryIdentifier historyId);
275
276     abstract void onTransactionAborted(TransactionIdentifier txId);
277
278     abstract void onTransactionCommitted(TransactionIdentifier txId);
279
280     abstract void onTransactionPurged(TransactionIdentifier txId);
281
282     /**
283      * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
284      *
285      * @param shard parent shard
286      * @return Leader frontend state
287      */
288     abstract @NonNull LeaderFrontendState toLeaderState(@NonNull Shard shard);
289
290     @Override
291     public final String toString() {
292         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
293     }
294
295     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
296         return helper.add("identifier", identifier);
297     }
298 }