/** * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.openflowplugin.impl.device; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.TransactionChain; import org.opendaylight.controller.md.sal.common.api.data.TransactionChainClosedException; import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.openflowplugin.api.openflow.device.DeviceInfo; import org.opendaylight.openflowplugin.api.openflow.lifecycle.LifecycleService; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * openflowplugin-impl * org.opendaylight.openflowplugin.impl.device * <p/> * Package protected class for controlling {@link WriteTransaction} life cycle. It is * a {@link TransactionChainListener} and provide package protected methods for writeToTransaction * method (wrapped {@link WriteTransaction#put(LogicalDatastoreType, InstanceIdentifier, DataObject)}) * and submitTransaction method (wrapped {@link WriteTransaction#submit()}) */ class TransactionChainManager implements TransactionChainListener, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(TransactionChainManager.class); private static final String CANNOT_WRITE_INTO_TRANSACTION = "Cannot write into transaction."; private final Object txLock = new Object(); private final DataBroker dataBroker; private final String nodeId; private LifecycleService lifecycleService; @GuardedBy("txLock") private WriteTransaction wTx; @GuardedBy("txLock") private BindingTransactionChain txChainFactory; @GuardedBy("txLock") private boolean submitIsEnabled; @GuardedBy("txLock") private ListenableFuture<Void> lastSubmittedFuture; private volatile boolean initCommit; @GuardedBy("txLock") private TransactionChainManagerStatus transactionChainManagerStatus = TransactionChainManagerStatus.SLEEPING; TransactionChainManager(@Nonnull final DataBroker dataBroker, @Nonnull final DeviceInfo deviceInfo) { this.dataBroker = dataBroker; this.nodeId = deviceInfo.getNodeInstanceIdentifier().getKey().getId().getValue(); this.lastSubmittedFuture = Futures.immediateFuture(null); } @GuardedBy("txLock") private void createTxChain() { BindingTransactionChain txChainFactoryTemp = txChainFactory; txChainFactory = dataBroker.createTransactionChain(TransactionChainManager.this); Optional.ofNullable(txChainFactoryTemp).ifPresent(TransactionChain::close); } public void setLifecycleService(final LifecycleService lifecycleService) { this.lifecycleService = lifecycleService; } boolean initialSubmitWriteTransaction() { enableSubmit(); return submitWriteTransaction(); } /** * Method change status for TxChainManager to {@link TransactionChainManagerStatus#WORKING} and it has to make * registration for this class instance as {@link TransactionChainListener} to provide possibility a make DS * transactions. Call this method for MASTER role only. */ void activateTransactionManager() { if (LOG.isDebugEnabled()) { LOG.debug("activateTransactionManager for node {} transaction submit is set to {}", this.nodeId, submitIsEnabled); } synchronized (txLock) { if (TransactionChainManagerStatus.SLEEPING == transactionChainManagerStatus) { Preconditions.checkState(txChainFactory == null, "TxChainFactory survive last close."); Preconditions.checkState(wTx == null, "We have some unexpected WriteTransaction."); this.transactionChainManagerStatus = TransactionChainManagerStatus.WORKING; this.submitIsEnabled = false; this.initCommit = true; createTxChain(); } } } /** * Method change status for TxChainManger to {@link TransactionChainManagerStatus#SLEEPING} and it unregisters * this class instance as {@link TransactionChainListener} so it broke a possibility to write something to DS. * Call this method for SLAVE only. * @return Future */ ListenableFuture<Void> deactivateTransactionManager() { if (LOG.isDebugEnabled()) { LOG.debug("deactivateTransactionManager for node {}", this.nodeId); } final ListenableFuture<Void> future; synchronized (txLock) { if (TransactionChainManagerStatus.WORKING == transactionChainManagerStatus) { transactionChainManagerStatus = TransactionChainManagerStatus.SLEEPING; future = txChainShuttingDown(); Preconditions.checkState(wTx == null, "We have some unexpected WriteTransaction."); Futures.addCallback(future, new FutureCallback<Void>() { @Override public void onSuccess(final Void result) { removeTxChainFactory(); } @Override public void onFailure(final Throwable t) { removeTxChainFactory(); } }); } else { // TODO : ignoring redundant deactivate invocation future = Futures.immediateCheckedFuture(null); } } return future; } private void removeTxChainFactory() { Optional.ofNullable(txChainFactory).ifPresent(TransactionChain::close); txChainFactory = null; } boolean submitWriteTransaction() { synchronized (txLock) { if (!submitIsEnabled) { if (LOG.isTraceEnabled()) { LOG.trace("transaction not committed - submit block issued"); } return false; } if (Objects.isNull(wTx)) { if (LOG.isTraceEnabled()) { LOG.trace("nothing to commit - submit returns true"); } return true; } Preconditions.checkState(TransactionChainManagerStatus.WORKING == transactionChainManagerStatus, "we have here Uncompleted Transaction for node {} and we are not MASTER", this.nodeId); final CheckedFuture<Void, TransactionCommitFailedException> submitFuture = wTx.submit(); lastSubmittedFuture = submitFuture; wTx = null; if (initCommit) { try { submitFuture.get(5L, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException ex) { LOG.error("Exception during INITIAL transaction submitting. ", ex); return false; } initCommit = false; return true; } Futures.addCallback(submitFuture, new FutureCallback<Void>() { @Override public void onSuccess(final Void result) { //NOOP } @Override public void onFailure(final Throwable t) { if (t instanceof TransactionCommitFailedException) { LOG.error("Transaction commit failed. ", t); } else { if (t instanceof CancellationException) { LOG.warn("Submit task was canceled"); LOG.trace("Submit exception: ", t); } else { LOG.error("Exception during transaction submitting. ", t); } } } }); } return true; } <T extends DataObject> void addDeleteOperationTotTxChain(final LogicalDatastoreType store, final InstanceIdentifier<T> path){ synchronized (txLock) { ensureTransaction(); if (wTx == null) { LOG.debug("WriteTx is null for node {}. Delete {} was not realized.", this.nodeId, path); throw new TransactionChainClosedException(CANNOT_WRITE_INTO_TRANSACTION); } wTx.delete(store, path); } } <T extends DataObject> void writeToTransaction(final LogicalDatastoreType store, final InstanceIdentifier<T> path, final T data, final boolean createParents){ synchronized (txLock) { ensureTransaction(); if (wTx == null) { LOG.debug("WriteTx is null for node {}. Write data for {} was not realized.", this.nodeId, path); throw new TransactionChainClosedException(CANNOT_WRITE_INTO_TRANSACTION); } wTx.put(store, path, data, createParents); } } @Override public void onTransactionChainFailed(final TransactionChain<?, ?> chain, final AsyncTransaction<?, ?> transaction, final Throwable cause) { synchronized (txLock) { if (TransactionChainManagerStatus.WORKING == transactionChainManagerStatus) { LOG.warn("Transaction chain failed, recreating chain due to ", cause); createTxChain(); wTx = null; } } } @Override public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) { // NOOP } @GuardedBy("txLock") @Nullable private void ensureTransaction() { if (wTx == null && TransactionChainManagerStatus.WORKING == transactionChainManagerStatus && txChainFactory != null) { wTx = txChainFactory.newWriteOnlyTransaction(); } } @VisibleForTesting void enableSubmit() { synchronized (txLock) { /* !!!IMPORTANT: never set true without txChainFactory */ submitIsEnabled = txChainFactory != null; } } ListenableFuture<Void> shuttingDown() { if (LOG.isDebugEnabled()) { LOG.debug("TxManager is going SHUTTING_DOWN for node {}", this.nodeId); } synchronized (txLock) { this.transactionChainManagerStatus = TransactionChainManagerStatus.SHUTTING_DOWN; return txChainShuttingDown(); } } @GuardedBy("txLock") private ListenableFuture<Void> txChainShuttingDown() { submitIsEnabled = false; ListenableFuture<Void> future; if (txChainFactory == null) { // stay with actual thread future = Futures.immediateCheckedFuture(null); } else if (wTx == null) { // hijack md-sal thread future = lastSubmittedFuture; } else { if (LOG.isDebugEnabled()) { LOG.debug("Submitting all transactions for Node {}", this.nodeId); } // hijack md-sal thread future = wTx.submit(); wTx = null; } return future; } @Override public void close() { if (LOG.isDebugEnabled()) { LOG.debug("Setting transactionChainManagerStatus to SHUTTING_DOWN for {}", this.nodeId); } synchronized (txLock) { removeTxChainFactory(); } } private enum TransactionChainManagerStatus { /** txChainManager is sleeping - is not active (SLAVE or default init value) */ WORKING, /** txChainManager is working - is active (MASTER) */ SLEEPING, /** txChainManager is trying to be closed - device disconnecting */ SHUTTING_DOWN; } }