Reimplement UnsignedLongRangeSet
[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.UnsignedLongSet;
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         private final Map<LocalHistoryIdentifier, FrontendHistoryMetadataBuilder> currentHistories = new HashMap<>();
88         private final LocalHistoryIdentifier standaloneId;
89         private final UnsignedLongSet purgedHistories;
90
91         Enabled(final String shardName, final ClientIdentifier identifier) {
92             super(shardName, identifier);
93
94             purgedHistories = UnsignedLongSet.of();
95
96             // History for stand-alone transactions is always present
97             standaloneId = standaloneHistoryId();
98             currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
99         }
100
101         Enabled(final String shardName, final FrontendClientMetadata meta) {
102             super(shardName, meta.getIdentifier());
103
104             purgedHistories = UnsignedLongSet.of(meta.getPurgedHistories());
105             for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
106                 final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(getIdentifier(), h);
107                 currentHistories.put(b.getIdentifier(), b);
108             }
109
110             // Sanity check and recovery
111             standaloneId = standaloneHistoryId();
112             if (!currentHistories.containsKey(standaloneId)) {
113                 LOG.warn("{}: Client {} recovered histories {} do not contain stand-alone history, attempting recovery",
114                     shardName, getIdentifier(), currentHistories);
115                 currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
116             }
117         }
118
119         @Override
120         public FrontendClientMetadata build() {
121             return new FrontendClientMetadata(getIdentifier(), purgedHistories.toRangeSet(),
122                 Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
123         }
124
125         @Override
126         void onHistoryCreated(final LocalHistoryIdentifier historyId) {
127             final FrontendHistoryMetadataBuilder newMeta = new FrontendHistoryMetadataBuilder(historyId);
128             final FrontendHistoryMetadataBuilder oldMeta = currentHistories.putIfAbsent(historyId, newMeta);
129             if (oldMeta != null) {
130                 // This should not be happening, warn about it
131                 LOG.warn("{}: Reused local history {}", shardName(), historyId);
132             } else {
133                 LOG.debug("{}: Created local history {}", shardName(), historyId);
134             }
135         }
136
137         @Override
138         void onHistoryClosed(final LocalHistoryIdentifier historyId) {
139             final FrontendHistoryMetadataBuilder builder = currentHistories.get(historyId);
140             if (builder != null) {
141                 builder.onHistoryClosed();
142                 LOG.debug("{}: Closed history {}", shardName(), historyId);
143             } else {
144                 LOG.warn("{}: Closed unknown history {}, ignoring", shardName(), historyId);
145             }
146         }
147
148         @Override
149         void onHistoryPurged(final LocalHistoryIdentifier historyId) {
150             final FrontendHistoryMetadataBuilder history = currentHistories.remove(historyId);
151             final long historyBits = historyId.getHistoryId();
152             if (history == null) {
153                 if (!purgedHistories.contains(historyBits)) {
154                     purgedHistories.add(historyBits);
155                     LOG.warn("{}: Purging unknown history {}", shardName(), historyId);
156                 } else {
157                     LOG.warn("{}: Duplicate purge of history {}", shardName(), historyId);
158                 }
159             } else {
160                 purgedHistories.add(historyBits);
161                 LOG.debug("{}: Purged history {}", shardName(), historyId);
162             }
163         }
164
165         @Override
166         void onTransactionAborted(final TransactionIdentifier txId) {
167             final FrontendHistoryMetadataBuilder history = getHistory(txId);
168             if (history != null) {
169                 history.onTransactionAborted(txId);
170                 LOG.debug("{}: Aborted transaction {}", shardName(), txId);
171             } else {
172                 LOG.warn("{}: Unknown history for aborted transaction {}, ignoring", shardName(), txId);
173             }
174         }
175
176         @Override
177         void onTransactionCommitted(final TransactionIdentifier txId) {
178             final FrontendHistoryMetadataBuilder history = getHistory(txId);
179             if (history != null) {
180                 history.onTransactionCommitted(txId);
181                 LOG.debug("{}: Committed transaction {}", shardName(), txId);
182             } else {
183                 LOG.warn("{}: Unknown history for commited transaction {}, ignoring", shardName(), txId);
184             }
185         }
186
187         @Override
188         void onTransactionPurged(final TransactionIdentifier txId) {
189             final FrontendHistoryMetadataBuilder history = getHistory(txId);
190             if (history != null) {
191                 history.onTransactionPurged(txId);
192                 LOG.debug("{}: Purged transaction {}", shardName(), txId);
193             } else {
194                 LOG.warn("{}: Unknown history for purged transaction {}, ignoring", shardName(), txId);
195             }
196         }
197
198         @Override
199         LeaderFrontendState toLeaderState(final Shard shard) {
200             // Note: we have to make sure to *copy* all current state and not leak any views, otherwise leader/follower
201             //       interactions would get intertwined leading to inconsistencies.
202             final Map<LocalHistoryIdentifier, LocalFrontendHistory> histories = new HashMap<>();
203             for (FrontendHistoryMetadataBuilder e : currentHistories.values()) {
204                 if (e.getIdentifier().getHistoryId() != 0) {
205                     final AbstractFrontendHistory state = e.toLeaderState(shard);
206                     verify(state instanceof LocalFrontendHistory, "Unexpected state %s", state);
207                     histories.put(e.getIdentifier(), (LocalFrontendHistory) state);
208                 }
209             }
210
211             final AbstractFrontendHistory singleHistory;
212             final FrontendHistoryMetadataBuilder singleHistoryMeta = currentHistories.get(
213                 new LocalHistoryIdentifier(getIdentifier(), 0));
214             if (singleHistoryMeta == null) {
215                 final ShardDataTree tree = shard.getDataStore();
216                 singleHistory = StandaloneFrontendHistory.create(shard.persistenceId(), getIdentifier(), tree);
217             } else {
218                 singleHistory = singleHistoryMeta.toLeaderState(shard);
219             }
220
221             return new LeaderFrontendState.Enabled(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
222                 purgedHistories.copy(), singleHistory, histories);
223         }
224
225         @Override
226         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
227             return super.addToStringAttributes(helper).add("current", currentHistories).add("purged", purgedHistories);
228         }
229
230         private FrontendHistoryMetadataBuilder getHistory(final TransactionIdentifier txId) {
231             LocalHistoryIdentifier historyId = txId.getHistoryId();
232             if (historyId.getHistoryId() == 0 && historyId.getCookie() != 0) {
233                 // We are pre-creating the history for free-standing transactions with a zero cookie, hence our lookup
234                 // needs to account for that.
235                 LOG.debug("{}: looking up {} instead of {}", shardName(), standaloneId, historyId);
236                 historyId = standaloneId;
237             }
238
239             return currentHistories.get(historyId);
240         }
241
242         private LocalHistoryIdentifier standaloneHistoryId() {
243             return new LocalHistoryIdentifier(getIdentifier(), 0);
244         }
245     }
246
247     private static final Logger LOG = LoggerFactory.getLogger(FrontendClientMetadataBuilder.class);
248
249     private final @NonNull ClientIdentifier identifier;
250     private final @NonNull String shardName;
251
252     FrontendClientMetadataBuilder(final String shardName, final ClientIdentifier identifier) {
253         this.shardName = requireNonNull(shardName);
254         this.identifier = requireNonNull(identifier);
255     }
256
257     static FrontendClientMetadataBuilder of(final String shardName, final FrontendClientMetadata meta) {
258         final Collection<FrontendHistoryMetadata> current = meta.getCurrentHistories();
259         final RangeSet<UnsignedLong> purged = meta.getPurgedHistories();
260
261         // Completely empty histories imply disabled state, as otherwise we'd have a record of the single history --
262         // either purged or active
263         return current.isEmpty() && purged.isEmpty() ? new Disabled(shardName, meta.getIdentifier())
264                 : new Enabled(shardName, meta);
265     }
266
267     @Override
268     public final ClientIdentifier getIdentifier() {
269         return identifier;
270     }
271
272     final String shardName() {
273         return shardName;
274     }
275
276     abstract void onHistoryCreated(LocalHistoryIdentifier historyId);
277
278     abstract void onHistoryClosed(LocalHistoryIdentifier historyId);
279
280     abstract void onHistoryPurged(LocalHistoryIdentifier historyId);
281
282     abstract void onTransactionAborted(TransactionIdentifier txId);
283
284     abstract void onTransactionCommitted(TransactionIdentifier txId);
285
286     abstract void onTransactionPurged(TransactionIdentifier txId);
287
288     /**
289      * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
290      *
291      * @param shard parent shard
292      * @return Leader frontend state
293      */
294     abstract @NonNull LeaderFrontendState toLeaderState(@NonNull Shard shard);
295
296     @Override
297     public final String toString() {
298         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
299     }
300
301     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
302         return helper.add("identifier", identifier);
303     }
304 }