ec2cdb6daa209a9f7ad2b2474ccf454166f9da57
[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 com.google.common.base.Preconditions;
11 import com.google.common.base.Verify;
12 import com.google.common.collect.Collections2;
13 import com.google.common.collect.Range;
14 import com.google.common.collect.RangeSet;
15 import com.google.common.collect.TreeRangeSet;
16 import com.google.common.primitives.UnsignedLong;
17 import java.util.HashMap;
18 import java.util.Map;
19 import javax.annotation.Nonnull;
20 import javax.annotation.concurrent.NotThreadSafe;
21 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
22 import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
23 import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
24 import org.opendaylight.controller.cluster.datastore.persisted.FrontendClientMetadata;
25 import org.opendaylight.controller.cluster.datastore.persisted.FrontendHistoryMetadata;
26 import org.opendaylight.yangtools.concepts.Builder;
27 import org.opendaylight.yangtools.concepts.Identifiable;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 @NotThreadSafe
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 RangeSet<UnsignedLong> purgedHistories;
37     private final ClientIdentifier identifier;
38
39     FrontendClientMetadataBuilder(final ClientIdentifier identifier) {
40         this.identifier = Preconditions.checkNotNull(identifier);
41         purgedHistories = TreeRangeSet.create();
42
43         // History for stand-alone transactions is always present
44         final LocalHistoryIdentifier standaloneId = standaloneHistoryId();
45         currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
46     }
47
48     FrontendClientMetadataBuilder(final FrontendClientMetadata meta) {
49         this.identifier = Preconditions.checkNotNull(meta.getIdentifier());
50         purgedHistories = TreeRangeSet.create(meta.getPurgedHistories());
51
52         for (FrontendHistoryMetadata h : meta.getCurrentHistories()) {
53             final FrontendHistoryMetadataBuilder b = new FrontendHistoryMetadataBuilder(identifier, h);
54             currentHistories.put(b.getIdentifier(), b);
55         }
56
57         // Sanity check and recovery
58         final LocalHistoryIdentifier standaloneId = standaloneHistoryId();
59         if (!currentHistories.containsKey(standaloneId)) {
60             LOG.warn("Client {} recovered histories {} do not contain stand-alone history, attempting recovery",
61                 identifier, currentHistories);
62             currentHistories.put(standaloneId, new FrontendHistoryMetadataBuilder(standaloneId));
63         }
64     }
65
66     private LocalHistoryIdentifier standaloneHistoryId() {
67         return new LocalHistoryIdentifier(identifier, 0);
68     }
69
70     @Override
71     public FrontendClientMetadata build() {
72         return new FrontendClientMetadata(identifier, purgedHistories,
73             Collections2.transform(currentHistories.values(), FrontendHistoryMetadataBuilder::build));
74     }
75
76     @Override
77     public ClientIdentifier getIdentifier() {
78         return identifier;
79     }
80
81     void onHistoryCreated(final LocalHistoryIdentifier historyId) {
82         final FrontendHistoryMetadataBuilder newMeta = new FrontendHistoryMetadataBuilder(historyId);
83         final FrontendHistoryMetadataBuilder oldMeta = currentHistories.putIfAbsent(historyId, newMeta);
84         if (oldMeta != null) {
85             // This should not be happening, warn about it
86             LOG.warn("Reused local history {}", historyId);
87         } else {
88             LOG.debug("Created local history {}", historyId);
89         }
90     }
91
92     void onHistoryClosed(final LocalHistoryIdentifier historyId) {
93         final FrontendHistoryMetadataBuilder builder = currentHistories.get(historyId);
94         if (builder != null) {
95             builder.onHistoryClosed();
96             LOG.debug("Closed history {}", historyId);
97         } else {
98             LOG.warn("Closed unknown history {}, ignoring", historyId);
99         }
100     }
101
102     void onHistoryPurged(final LocalHistoryIdentifier historyId) {
103         final FrontendHistoryMetadataBuilder history = currentHistories.remove(historyId);
104         if (history == null) {
105             LOG.warn("Purging unknown history {}", historyId);
106         }
107
108         // XXX: do we need to account for cookies?
109         purgedHistories.add(Range.singleton(UnsignedLong.fromLongBits(historyId.getHistoryId())));
110         LOG.debug("Purged history {}", historyId);
111     }
112
113     void onTransactionAborted(final TransactionIdentifier txId) {
114         final FrontendHistoryMetadataBuilder history = getHistory(txId);
115         if (history != null) {
116             history.onTransactionAborted(txId);
117             LOG.debug("Committed transaction {}", txId);
118         } else {
119             LOG.warn("Unknown history for aborted transaction {}, ignoring", txId);
120         }
121     }
122
123     void onTransactionCommitted(final TransactionIdentifier txId) {
124         final FrontendHistoryMetadataBuilder history = getHistory(txId);
125         if (history != null) {
126             history.onTransactionCommitted(txId);
127             LOG.debug("Aborted transaction {}", txId);
128         } else {
129             LOG.warn("Unknown history for commited transaction {}, ignoring", txId);
130         }
131     }
132
133     void onTransactionPurged(final TransactionIdentifier txId) {
134         final FrontendHistoryMetadataBuilder history = getHistory(txId);
135         if (history != null) {
136             history.onTransactionPurged(txId);
137             LOG.debug("Purged transaction {}", txId);
138         } else {
139             LOG.warn("Unknown history for purged transaction {}, ignoring", txId);
140         }
141     }
142
143     /**
144      * Transform frontend metadata for a particular client into its {@link LeaderFrontendState} counterpart.
145      *
146      * @param shard parent shard
147      * @return Leader frontend state
148      */
149     @Nonnull LeaderFrontendState toLeaderState(@Nonnull final Shard shard) {
150         // Note: we have to make sure to *copy* all current state and not leak any views, otherwise leader/follower
151         //       interactions would get intertwined leading to inconsistencies.
152         final Map<LocalHistoryIdentifier, LocalFrontendHistory> histories = new HashMap<>();
153         for (FrontendHistoryMetadataBuilder e : currentHistories.values()) {
154             if (e.getIdentifier().getHistoryId() != 0) {
155                 final AbstractFrontendHistory state = e.toLeaderState(shard);
156                 Verify.verify(state instanceof LocalFrontendHistory);
157                 histories.put(e.getIdentifier(), (LocalFrontendHistory) state);
158             }
159         }
160
161         final AbstractFrontendHistory singleHistory;
162         final FrontendHistoryMetadataBuilder singleHistoryMeta = currentHistories.get(
163             new LocalHistoryIdentifier(identifier, 0));
164         if (singleHistoryMeta == null) {
165             final ShardDataTree tree = shard.getDataStore();
166             singleHistory = StandaloneFrontendHistory.create(shard.persistenceId(), getIdentifier(), tree);
167         } else {
168             singleHistory = singleHistoryMeta.toLeaderState(shard);
169         }
170
171         return new LeaderFrontendState(shard.persistenceId(), getIdentifier(), shard.getDataStore(),
172             TreeRangeSet.create(purgedHistories), singleHistory, histories);
173     }
174
175     private FrontendHistoryMetadataBuilder getHistory(final TransactionIdentifier txId) {
176         return currentHistories.get(txId.getHistoryId());
177     }
178 }