2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.openflowplugin.common.txchain;
10 import com.google.common.base.Preconditions;
11 import com.google.common.util.concurrent.FluentFuture;
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.ListenableFuture;
14 import com.google.common.util.concurrent.MoreExecutors;
15 import java.util.Optional;
16 import java.util.concurrent.CancellationException;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20 import java.util.concurrent.locks.ReadWriteLock;
21 import java.util.concurrent.locks.ReentrantReadWriteLock;
22 import org.checkerframework.checker.lock.qual.GuardedBy;
23 import org.checkerframework.checker.lock.qual.Holding;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.opendaylight.mdsal.binding.api.DataBroker;
26 import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
27 import org.opendaylight.mdsal.binding.api.Transaction;
28 import org.opendaylight.mdsal.binding.api.TransactionChain;
29 import org.opendaylight.mdsal.binding.api.TransactionChainClosedException;
30 import org.opendaylight.mdsal.binding.api.TransactionChainListener;
31 import org.opendaylight.mdsal.binding.api.WriteTransaction;
32 import org.opendaylight.mdsal.common.api.CommitInfo;
33 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.yangtools.yang.binding.DataObject;
35 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * The openflowplugin-impl.org.opendaylight.openflowplugin.impl.device
41 * package protected class for controlling {@link WriteTransaction} life cycle. It is
42 * a {@link TransactionChainListener} and provide package protected methods for writeToTransaction
43 * method (wrapped {@link WriteTransaction#put(LogicalDatastoreType, InstanceIdentifier, DataObject)})
44 * and submitTransaction method (wrapped {@link WriteTransaction#commit()}).
46 public class TransactionChainManager implements TransactionChainListener, AutoCloseable {
48 private static final Logger LOG = LoggerFactory.getLogger(TransactionChainManager.class);
49 private static final String CANNOT_WRITE_INTO_TRANSACTION = "Cannot write into transaction.";
51 private final Object txLock = new Object();
52 private final DataBroker dataBroker;
53 private final String nodeId;
56 private ReadWriteTransaction writeTx;
58 private TransactionChain transactionChain;
60 private boolean submitIsEnabled;
62 private FluentFuture<? extends CommitInfo> lastSubmittedFuture;
64 private volatile boolean initCommit;
67 private TransactionChainManagerStatus transactionChainManagerStatus = TransactionChainManagerStatus.SLEEPING;
68 private ReadWriteLock readWriteTransactionLock = new ReentrantReadWriteLock();
70 public TransactionChainManager(@NonNull final DataBroker dataBroker,
71 @NonNull final String deviceIdentifier) {
72 this.dataBroker = dataBroker;
73 this.nodeId = deviceIdentifier;
74 this.lastSubmittedFuture = CommitInfo.emptyFluentFuture();
78 private void createTxChain() {
79 TransactionChain txChainFactoryTemp = transactionChain;
80 transactionChain = dataBroker.createTransactionChain(TransactionChainManager.this);
81 if (txChainFactoryTemp != null) {
82 txChainFactoryTemp.close();
86 public boolean initialSubmitWriteTransaction() {
88 return submitTransaction();
92 * Method change status for TxChainManager to WORKING and it has to make
93 * registration for this class instance as {@link TransactionChainListener} to provide possibility a make DS
94 * transactions. Call this method for MASTER role only.
96 public void activateTransactionManager() {
97 if (LOG.isDebugEnabled()) {
98 LOG.debug("activateTransactionManager for node {} transaction submit is set to {}",
99 this.nodeId, submitIsEnabled);
101 synchronized (txLock) {
102 if (TransactionChainManagerStatus.SLEEPING == transactionChainManagerStatus) {
103 Preconditions.checkState(transactionChain == null,
104 "TxChainFactory survive last close.");
105 Preconditions.checkState(writeTx == null,
106 "We have some unexpected WriteTransaction.");
107 this.transactionChainManagerStatus = TransactionChainManagerStatus.WORKING;
108 this.submitIsEnabled = false;
109 this.initCommit = true;
116 * Method change status for TxChainManger to SLEEPING and it unregisters
117 * this class instance as {@link TransactionChainListener} so it broke a possibility to write something to DS.
118 * Call this method for SLAVE only.
121 public FluentFuture<?> deactivateTransactionManager() {
122 if (LOG.isDebugEnabled()) {
123 LOG.debug("deactivateTransactionManager for node {}", this.nodeId);
125 final FluentFuture<? extends CommitInfo> future;
126 synchronized (txLock) {
127 if (TransactionChainManagerStatus.WORKING == transactionChainManagerStatus) {
128 transactionChainManagerStatus = TransactionChainManagerStatus.SLEEPING;
129 future = txChainShuttingDown();
130 Preconditions.checkState(writeTx == null,
131 "We have some unexpected WriteTransaction.");
132 future.addCallback(new FutureCallback<CommitInfo>() {
134 public void onSuccess(final CommitInfo result) {
135 closeTransactionChain();
139 public void onFailure(@NonNull final Throwable throwable) {
140 closeTransactionChain();
142 }, MoreExecutors.directExecutor());
144 // ignoring redundant deactivate invocation
145 future = CommitInfo.emptyFluentFuture();
151 private void closeTransactionChain() {
152 if (writeTx != null) {
156 if (transactionChain != null) {
157 transactionChain.close();
158 transactionChain = null;
163 public boolean submitTransaction() {
164 return submitTransaction(false);
168 public boolean submitTransaction(boolean doSync) {
169 synchronized (txLock) {
170 if (!submitIsEnabled) {
171 LOG.trace("transaction not committed - submit block issued");
174 if (writeTx == null) {
175 LOG.trace("nothing to commit - submit returns true");
178 Preconditions.checkState(TransactionChainManagerStatus.WORKING == transactionChainManagerStatus,
179 "we have here Uncompleted Transaction for node {} and we are not MASTER",
181 final FluentFuture<? extends CommitInfo> submitFuture = writeTx.commit();
182 lastSubmittedFuture = submitFuture;
185 if (initCommit || doSync) {
187 submitFuture.get(5L, TimeUnit.SECONDS);
188 } catch (InterruptedException | ExecutionException | TimeoutException ex) {
189 LOG.error("Exception during INITIAL({}) || doSync({}) transaction submitting. ",
190 initCommit, doSync, ex);
197 submitFuture.addCallback(new FutureCallback<CommitInfo>() {
199 public void onSuccess(final CommitInfo result) {
204 public void onFailure(final Throwable throwable) {
205 if (throwable instanceof InterruptedException || throwable instanceof ExecutionException) {
206 LOG.error("Transaction commit failed. ", throwable);
208 if (throwable instanceof CancellationException) {
209 LOG.warn("Submit task was canceled");
210 LOG.trace("Submit exception: ", throwable);
212 LOG.error("Exception during transaction submitting. ", throwable);
216 }, MoreExecutors.directExecutor());
221 public <T extends DataObject> void addDeleteOperationToTxChain(final LogicalDatastoreType store,
222 final InstanceIdentifier<T> path) {
223 synchronized (txLock) {
225 if (writeTx == null) {
226 LOG.debug("WriteTx is null for node {}. Delete {} was not realized.", this.nodeId, path);
227 throw new TransactionChainClosedException(CANNOT_WRITE_INTO_TRANSACTION);
230 writeTx.delete(store, path);
234 public <T extends DataObject> void writeToTransaction(final LogicalDatastoreType store,
235 final InstanceIdentifier<T> path,
237 final boolean createParents) {
238 synchronized (txLock) {
240 if (writeTx == null) {
241 LOG.debug("WriteTx is null for node {}. Write data for {} was not realized.", this.nodeId, path);
242 throw new TransactionChainClosedException(CANNOT_WRITE_INTO_TRANSACTION);
245 writeTx.put(store, path, data, createParents);
249 public <T extends DataObject> void mergeToTransaction(final LogicalDatastoreType store,
250 final InstanceIdentifier<T> path,
252 final boolean createParents) {
253 synchronized (txLock) {
255 if (writeTx == null) {
256 LOG.debug("WriteTx is null for node {}. Merge data for {} was not realized.", this.nodeId, path);
257 throw new TransactionChainClosedException(CANNOT_WRITE_INTO_TRANSACTION);
260 writeTx.merge(store, path, data, createParents);
264 public <T extends DataObject> ListenableFuture<Optional<T>>
265 readFromTransaction(final LogicalDatastoreType store, final InstanceIdentifier<T> path) {
266 synchronized (txLock) {
268 if (writeTx == null) {
269 LOG.debug("WriteTx is null for node {}. Read data for {} was not realized.", this.nodeId, path);
270 throw new TransactionChainClosedException(CANNOT_WRITE_INTO_TRANSACTION);
273 return writeTx.read(store, path);
278 public void onTransactionChainFailed(final TransactionChain chain,
279 final Transaction transaction, final Throwable cause) {
280 synchronized (txLock) {
281 if (TransactionChainManagerStatus.WORKING == transactionChainManagerStatus
282 && chain.equals(this.transactionChain)) {
283 LOG.warn("Transaction chain failed, recreating chain due to ", cause);
284 closeTransactionChain();
292 public void onTransactionChainSuccessful(final TransactionChain chain) {
297 private void ensureTransaction() {
298 if (writeTx == null && TransactionChainManagerStatus.WORKING == transactionChainManagerStatus
299 && transactionChain != null) {
300 writeTx = transactionChain.newReadWriteTransaction();
304 private void enableSubmit() {
305 synchronized (txLock) {
306 /* !!!IMPORTANT: never set true without transactionChain */
307 submitIsEnabled = transactionChain != null;
311 public FluentFuture<?> shuttingDown() {
312 if (LOG.isDebugEnabled()) {
313 LOG.debug("TxManager is going SHUTTING_DOWN for node {}", this.nodeId);
315 synchronized (txLock) {
316 this.transactionChainManagerStatus = TransactionChainManagerStatus.SHUTTING_DOWN;
317 return txChainShuttingDown();
322 private FluentFuture<? extends CommitInfo> txChainShuttingDown() {
323 boolean wasSubmitEnabled = submitIsEnabled;
324 submitIsEnabled = false;
325 FluentFuture<? extends CommitInfo> future;
327 if (!wasSubmitEnabled || transactionChain == null) {
328 // stay with actual thread
329 future = CommitInfo.emptyFluentFuture();
331 if (writeTx != null) {
335 } else if (writeTx == null) {
336 // hijack md-sal thread
337 future = lastSubmittedFuture;
339 if (LOG.isDebugEnabled()) {
340 LOG.debug("Submitting all transactions for Node {}", this.nodeId);
342 // hijack md-sal thread
343 future = writeTx.commit();
351 public void close() {
352 if (LOG.isDebugEnabled()) {
353 LOG.debug("Setting transactionChainManagerStatus to SHUTTING_DOWN for {}", this.nodeId);
355 synchronized (txLock) {
356 closeTransactionChain();
360 private enum TransactionChainManagerStatus {
362 * txChainManager is working - is active (MASTER).
366 * txChainManager is sleeping - is not active (SLAVE or default init value).
370 * txChainManager is trying to be closed - device disconnecting.
375 public void acquireWriteTransactionLock() {
376 readWriteTransactionLock.writeLock().lock();
379 public void releaseWriteTransactionLock() {
380 readWriteTransactionLock.writeLock().unlock();