org.opendaylight.distributed.tx.impl.DtxImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.distributed.tx.impl.DtxImpl.java

Source

/*
 * 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.distributed.tx.impl;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.util.concurrent.*;
import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.distributed.tx.api.DTXLogicalTXProviderType;
import org.opendaylight.distributed.tx.api.DTx;
import org.opendaylight.distributed.tx.api.DTxException;
import org.opendaylight.distributed.tx.spi.*;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DtxImpl implements DTx {
    private static final Logger LOG = LoggerFactory.getLogger(DTxProviderImpl.class);
    private final Map<DTXLogicalTXProviderType, Map<InstanceIdentifier<?>, CachingReadWriteTx>> perNodeTransactionsbyLogicalType;
    private final Map<DTXLogicalTXProviderType, TxProvider> txProviderMap;
    private final Map<InstanceIdentifier<?>, ReadWriteTransaction> readWriteTxMap = new HashMap<InstanceIdentifier<?>, ReadWriteTransaction>();
    private final TransactionLock deviceLock;

    public DtxImpl(@Nonnull final TxProvider txProvider, @Nonnull final Set<InstanceIdentifier<?>> nodes,
            TransactionLock lock) {
        Preconditions.checkArgument(!nodes.isEmpty(), "Cannot create distributed tx for 0 nodes");
        txProviderMap = new HashMap<>();
        this.txProviderMap.put(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER, txProvider);
        Map<DTXLogicalTXProviderType, Set<InstanceIdentifier<?>>> internalNodeMap = new HashMap<>(1);
        internalNodeMap.put(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER, nodes);
        this.perNodeTransactionsbyLogicalType = initializeTransactionsPerLogicalType(this.txProviderMap,
                internalNodeMap);
        this.deviceLock = lock;
    }

    public DtxImpl(@Nonnull final Map<DTXLogicalTXProviderType, TxProvider> providerMap,
            @Nonnull final Map<DTXLogicalTXProviderType, Set<InstanceIdentifier<?>>> nodesMap,
            TransactionLock lock) {
        Preconditions.checkArgument(!nodesMap.values().isEmpty(), "Cannot create distributed tx for 0 nodes");
        Preconditions.checkArgument(providerMap.keySet().containsAll(nodesMap.keySet()),
                "logicalType sets of txPoviders and nodes are different");
        this.txProviderMap = providerMap;
        perNodeTransactionsbyLogicalType = initializeTransactionsPerLogicalType(providerMap, nodesMap);
        this.deviceLock = lock;
    }

    private TxProvider getTxProviderByType(DTXLogicalTXProviderType type) {
        return this.txProviderMap.get(type);
    }

    private Map<DTXLogicalTXProviderType, Map<InstanceIdentifier<?>, CachingReadWriteTx>> initializeTransactionsPerLogicalType(
            final Map<DTXLogicalTXProviderType, TxProvider> txProviderMap,
            Map<DTXLogicalTXProviderType, Set<InstanceIdentifier<?>>> nodesMap) {
        Map<DTXLogicalTXProviderType, Map<InstanceIdentifier<?>, CachingReadWriteTx>> typeCacheMap = new HashMap<>(
                txProviderMap.keySet().size());

        for (DTXLogicalTXProviderType type : nodesMap.keySet()) {
            Set<InstanceIdentifier<?>> nodes = nodesMap.get(type);
            final DTXLogicalTXProviderType t = type;
            Map<InstanceIdentifier<?>, CachingReadWriteTx> tmpMap = Maps.toMap(nodes,
                    new Function<InstanceIdentifier<?>, CachingReadWriteTx>() {
                        @Nullable
                        @Override
                        public CachingReadWriteTx apply(@Nullable final InstanceIdentifier<?> input) {
                            ReadWriteTransaction tx = getTxProviderByType(t).newTx(input);
                            readWriteTxMap.put(input, tx);
                            return new CachingReadWriteTx(tx);
                        }
                    });
            typeCacheMap.put(type, tmpMap);
        }

        return typeCacheMap;
    }

    // This is a method for unit test
    public int getSizeofCacheByNodeId(InstanceIdentifier<?> nodeId) {
        Preconditions.checkArgument(perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).containsKey(nodeId),
                "Unknown node: %s. Not in transaction", nodeId);
        return getSizeofCacheByNodeIdAndType(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER, nodeId);
    }

    // This is a method for unit test
    public int getSizeofCacheByNodeIdAndType(DTXLogicalTXProviderType type, InstanceIdentifier<?> nodeId) {
        Preconditions.checkArgument(containsIid(nodeId), "Unknown node: %s. Not in transaction", nodeId);
        return perNodeTransactionsbyLogicalType.get(type).get(nodeId).getSizeOfCache();
    }

    private void waitForAllTxsDone() {
        for (DTXLogicalTXProviderType type : this.perNodeTransactionsbyLogicalType.keySet()) {
            for (CachingReadWriteTx perNodeTx : this.perNodeTransactionsbyLogicalType.get(type).values()) {
                perNodeTx.waitForAllActiveOperationsDone();
            }
        }
    }

    @Deprecated
    @Override
    public void delete(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<?> instanceIdentifier, final InstanceIdentifier<?> nodeId)
            throws DTxException.EditFailedException {

        Preconditions.checkArgument(perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).containsKey(nodeId),
                "Unknown node: %s. Not in transaction", nodeId);
        final ReadWriteTransaction transaction = perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).get(nodeId);
        transaction.delete(logicalDatastoreType, instanceIdentifier);
    }

    @Deprecated
    @Override
    public void delete(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<?> instanceIdentifier) throws DTxException.EditFailedException {
        throw new UnsupportedOperationException("Unimplemented");
    }

    @Deprecated
    @Override
    public <T extends DataObject> void merge(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t)
            throws DTxException.EditFailedException, DTxException.RollbackFailedException {
        throw new UnsupportedOperationException("Unimplemented");
    }

    @Deprecated
    @Override
    public <T extends DataObject> void merge(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t, final boolean b)
            throws DTxException.EditFailedException {
        throw new UnsupportedOperationException("Unimplemented");
    }

    @Deprecated
    @Override
    public <T extends DataObject> void put(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t) throws DTxException.EditFailedException {
        throw new UnsupportedOperationException("Unimplemented");
    }

    @Deprecated
    @Override
    public <T extends DataObject> void put(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t, final boolean b)
            throws DTxException.EditFailedException {
        throw new UnsupportedOperationException("Unimplemented");

    }

    @Deprecated
    @Override
    public <T extends DataObject> void merge(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t, final InstanceIdentifier<?> nodeId)
            throws DTxException.EditFailedException {
        Preconditions.checkArgument(perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).containsKey(nodeId),
                "Unknown node: %s. Not in transaction", nodeId);
        final ReadWriteTransaction transaction = perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).get(nodeId);
        transaction.merge(logicalDatastoreType, instanceIdentifier, t);
    }

    @Deprecated
    @Override
    public <T extends DataObject> void merge(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t, final boolean b,
            final InstanceIdentifier<?> nodeId) throws DTxException.EditFailedException {

    }

    @Deprecated
    @Override
    public <T extends DataObject> void put(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t, final InstanceIdentifier<?> nodeId)
            throws DTxException.EditFailedException {
        Preconditions.checkArgument(perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).containsKey(nodeId),
                "Unknown node: %s. Not in transaction", nodeId);
        final ReadWriteTransaction transaction = perNodeTransactionsbyLogicalType
                .get(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER).get(nodeId);
        transaction.put(logicalDatastoreType, instanceIdentifier, t);
    }

    @Deprecated
    @Override
    public <T extends DataObject> void put(final LogicalDatastoreType logicalDatastoreType,
            final InstanceIdentifier<T> instanceIdentifier, final T t, final boolean b,
            final InstanceIdentifier<?> nodeId) throws DTxException.EditFailedException {

    }

    //FIXME!! Fangming add double submit comments
    @Override
    public CheckedFuture<Void, TransactionCommitFailedException> submit()
            throws DTxException.SubmitFailedException, DTxException.RollbackFailedException {
        waitForAllTxsDone();
        int totalSubmitSize = getNumberofNodes();

        final Map<InstanceIdentifier<?>, PerNodeTxState> commitStatus = Maps
                .newHashMapWithExpectedSize(totalSubmitSize);
        final SettableFuture<Void> distributedSubmitFuture = SettableFuture.create();

        for (DTXLogicalTXProviderType type : this.perNodeTransactionsbyLogicalType.keySet()) {
            Map<InstanceIdentifier<?>, CachingReadWriteTx> transactions = this.perNodeTransactionsbyLogicalType
                    .get(type);

            for (final Map.Entry<InstanceIdentifier<?>, CachingReadWriteTx> perNodeTx : transactions.entrySet()) {
                CheckedFuture<Void, TransactionCommitFailedException> submitFuture = null;
                try {
                    submitFuture = perNodeTx.getValue().submit();
                } catch (Exception submitFailException) {
                    new PerNodeSubmitCallback(type, commitStatus, perNodeTx, distributedSubmitFuture)
                            .failedWithException(submitFailException);
                    continue;
                }
                Futures.addCallback(submitFuture,
                        new PerNodeSubmitCallback(type, commitStatus, perNodeTx, distributedSubmitFuture));
            }
        }

        return Futures.makeChecked(distributedSubmitFuture,
                new Function<Exception, TransactionCommitFailedException>() {
                    @Nullable
                    @Override
                    public TransactionCommitFailedException apply(@Nullable final Exception input) {
                        return new TransactionCommitFailedException(
                                "Submit failed. Check nested exception for rollback status", input);
                    }
                });
    }

    /**
     * Perform submit rollback with the caches and empty rollback transactions for every node
     */
    private CheckedFuture<Void, DTxException.RollbackFailedException> rollbackUponCommitFailure(
            final Map<InstanceIdentifier<?>, PerNodeTxState> commitStatus) {

        Map<InstanceIdentifier<?>, CachingReadWriteTx> perNodeCache = new HashMap<>();

        for (DTXLogicalTXProviderType type : this.perNodeTransactionsbyLogicalType.keySet()) {
            Map<InstanceIdentifier<?>, CachingReadWriteTx> tmpMap = this.perNodeTransactionsbyLogicalType.get(type);
            perNodeCache.putAll(tmpMap);
        }

        Rollback rollback = new RollbackImpl();
        final ListenableFuture<Void> rollbackFuture = rollback.rollback(perNodeCache,
                Maps.transformValues(commitStatus, new Function<PerNodeTxState, ReadWriteTransaction>() {
                    @Nullable
                    @Override
                    public ReadWriteTransaction apply(@Nullable final PerNodeTxState input) {
                        return input.getRollbackTx();
                    }
                }));

        return Futures.makeChecked(rollbackFuture, new Function<Exception, DTxException.RollbackFailedException>() {
            @Nullable
            @Override
            public DTxException.RollbackFailedException apply(@Nullable final Exception input) {
                return new DTxException.RollbackFailedException(input);
            }
        });
    }

    private CheckedFuture<Void, DTxException.RollbackFailedException> rollbackUponOperationFailure() {
        waitForAllTxsDone();
        Rollback rollback = new RollbackImpl();
        Map<InstanceIdentifier<?>, CachingReadWriteTx> perNodeCache = new HashMap<>();

        for (DTXLogicalTXProviderType type : this.perNodeTransactionsbyLogicalType.keySet()) {
            Map<InstanceIdentifier<?>, CachingReadWriteTx> tmpMap = this.perNodeTransactionsbyLogicalType.get(type);
            perNodeCache.putAll(tmpMap);
        }

        final ListenableFuture<Void> rollbackFuture = rollback.rollback(perNodeCache, this.readWriteTxMap);

        return Futures.makeChecked(rollbackFuture, new Function<Exception, DTxException.RollbackFailedException>() {
            @Nullable
            @Override
            public DTxException.RollbackFailedException apply(@Nullable final Exception input) {
                return new DTxException.RollbackFailedException(input);
            }
        });
    }

    private void dtxReleaseDevices() {
        Map<DTXLogicalTXProviderType, Set<InstanceIdentifier<?>>> devices = Maps.transformValues(
                perNodeTransactionsbyLogicalType,
                new Function<Map<InstanceIdentifier<?>, CachingReadWriteTx>, Set<InstanceIdentifier<?>>>() {
                    @Nullable
                    @Override
                    public Set<InstanceIdentifier<?>> apply(
                            @Nullable Map<InstanceIdentifier<?>, CachingReadWriteTx> input) {
                        return input.keySet();
                    }
                });
        deviceLock.releaseDevices(devices);
    }

    @Deprecated
    @Override
    public ListenableFuture<RpcResult<TransactionStatus>> commit() {
        throw new UnsupportedOperationException("Deprecated");
    }

    @Override
    public boolean cancel() throws DTxException.RollbackFailedException {
        throw new UnsupportedOperationException("Deprecated");
    }

    @Override
    public Object getIdentifier() {
        return getIdentifierSet();
    }

    private Set<InstanceIdentifier<?>> getIdentifierSet() {
        Set<InstanceIdentifier<?>> set = new HashSet<>();

        for (DTXLogicalTXProviderType type : DTXLogicalTXProviderType.values()) {
            if (this.perNodeTransactionsbyLogicalType.containsKey(type)) {
                set.addAll(this.perNodeTransactionsbyLogicalType.get(type).keySet());
            }
        }

        return set;
    }

    private int getNumberofNodes() {
        int totalSubmitSize = 0;

        for (DTXLogicalTXProviderType type : this.perNodeTransactionsbyLogicalType.keySet()) {
            totalSubmitSize += perNodeTransactionsbyLogicalType.get(type).size();
        }

        return totalSubmitSize;
    }

    private TxProvider getTxProviderByLogicalType(DTXLogicalTXProviderType type) {
        Preconditions.checkArgument(this.txProviderMap.containsKey(type), "can't find key");
        return this.txProviderMap.get(type);
    }

    private class PerNodeSubmitCallback implements FutureCallback<Void> {
        private final Map<InstanceIdentifier<?>, PerNodeTxState> commitStatus;
        private final Map.Entry<InstanceIdentifier<?>, CachingReadWriteTx> perNodeTx;
        private final SettableFuture<Void> distributedSubmitFuture;
        private final DTXLogicalTXProviderType logicalTxProviderType;
        final ExecutorService executor = Executors.newSingleThreadExecutor();

        public PerNodeSubmitCallback(DTXLogicalTXProviderType type,
                final Map<InstanceIdentifier<?>, PerNodeTxState> commitStatus,
                final Map.Entry<InstanceIdentifier<?>, CachingReadWriteTx> perNodeTx,
                final SettableFuture<Void> distributedSubmitFuture) {
            this.commitStatus = commitStatus;
            this.perNodeTx = perNodeTx;
            this.distributedSubmitFuture = distributedSubmitFuture;
            logicalTxProviderType = type;
        }

        /**
         * Callback for per-node transaction submit success
         * Prepare potential rollback per-node transaction (rollback will be performed in a dedicated Tx)
         */
        @Override
        public void onSuccess(@Nullable final Void result) {
            LOG.trace("Per node tx({}/{}) executed successfully for: {}", commitStatus.size(), getNumberofNodes(),
                    perNodeTx.getKey());

            final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(executor);

            final ListenableFuture txProviderFuture = executorService.submit(new Callable() {
                @Override
                public Object call() throws Exception {
                    final ReadWriteTransaction readWriteTransaction = getTxProviderByLogicalType(
                            logicalTxProviderType).newTx(perNodeTx.getKey());
                    final PerNodeTxState status = PerNodeTxState.createSuccess(readWriteTransaction);
                    synchronized (commitStatus) {
                        commitStatus.put(perNodeTx.getKey(), status);
                    }
                    checkTransactionStatus();
                    return null;
                }
            });

            Futures.addCallback(txProviderFuture, new FutureCallback() {
                @Override
                public void onSuccess(@Nullable Object result) {
                    LOG.trace("Per node new tx successfully");
                }

                @Override
                public void onFailure(Throwable t) {
                    LOG.trace("Per node error to relock the device. ignore");
                }
            });
        }

        /**
         * Invoked when this distributed transaction cannot open a post submit transaction (for performing potential rollback)
         */
        private void handleRollbackTxCreationException(final TxException.TxInitiatizationFailedException e) {
            LOG.warn("Unable to create post submit transaction for node: {}. Distributed transaction failing",
                    perNodeTx.getKey(), e);
            distributedSubmitFuture.setException(new DTxException.SubmitFailedException(
                    Collections.<InstanceIdentifier<?>>singleton(perNodeTx.getKey()), e));
        }

        /**
         * Callback for per-node transaction submit FAIL
         * Prepare rollback per-node transaction (rollback will be performed in a dedicated Tx)
         */
        @Override
        public void onFailure(final Throwable t) {
            failedWithException(t);
        }

        public void failedWithException(final Throwable t) {
            LOG.warn("Per node tx executed failed for: {}", perNodeTx.getKey(), t);

            final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(executor);

            final ListenableFuture txProviderFuture = executorService.submit(new Callable() {
                @Override
                public Object call() throws Exception {
                    try {
                        final ReadWriteTransaction readWriteTransaction = getTxProviderByLogicalType(
                                logicalTxProviderType).newTx(perNodeTx.getKey());
                        synchronized (commitStatus) {
                            commitStatus.put(perNodeTx.getKey(),
                                    PerNodeTxState.createFailed(t, readWriteTransaction));
                        }
                        checkTransactionStatus();

                    } catch (TxException.TxInitiatizationFailedException e) {
                        handleRollbackTxCreationException(e);
                    }
                    return null;
                }
            });
        }

        /**
         * Check the overall status of distributed Tx after each per-node transaction status change
         */
        private void checkTransactionStatus() {
            try {
                final DistributedSubmitState txState = validate(commitStatus, getIdentifierSet());

                switch (txState) {
                case WAITING: {
                    return;
                }
                case SUCCESS: {
                    this.releaseTx();
                    dtxReleaseDevices();
                    distributedSubmitFuture.set(null);
                    return;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported " + txState);
                }
                }

            } catch (final DTxException.SubmitFailedException e) {
                Futures.addCallback(rollbackUponCommitFailure(commitStatus), new FutureCallback<Void>() {
                    @Override
                    public void onSuccess(@Nullable final Void result) {
                        LOG.trace("Distributed tx failed for {}. Rollback was successful", perNodeTx.getKey());
                        dtxReleaseDevices();
                        distributedSubmitFuture.setException(e);
                    }

                    @Override
                    public void onFailure(final Throwable t) {
                        LOG.warn("Distributed tx filed. Rollback FAILED. Device(s) state is unknown", t);
                        dtxReleaseDevices();
                        distributedSubmitFuture.setException(t);
                    }
                });
            }
        }

        /**
         * Validate distributed Tx status. Either waiting, success-all or fail indicated by an exception
         */
        private DistributedSubmitState validate(final Map<InstanceIdentifier<?>, PerNodeTxState> commitStatus,
                final Set<InstanceIdentifier<?>> instanceIdentifiers) throws DTxException.SubmitFailedException {
            boolean submitDone = false;
            synchronized (commitStatus) {
                if (commitStatus.size() == instanceIdentifiers.size())
                    submitDone = true;
            }
            if (submitDone) {
                LOG.debug("Distributed tx submit finished with status: {}", commitStatus);
                final Map<InstanceIdentifier<?>, PerNodeTxState> failedSubmits = Maps.filterEntries(commitStatus,
                        new Predicate<Map.Entry<InstanceIdentifier<?>, PerNodeTxState>>() {
                            @Override
                            public boolean apply(final Map.Entry<InstanceIdentifier<?>, PerNodeTxState> input) {
                                return !input.getValue().isSuccess();
                            }
                        });

                if (!failedSubmits.isEmpty()) {
                    throw new DTxException.SubmitFailedException(failedSubmits.keySet());
                } else {
                    return DistributedSubmitState.SUCCESS;
                }
            }

            return DistributedSubmitState.WAITING;
        }

        private void releaseTx() {
            for (Map.Entry<InstanceIdentifier<?>, PerNodeTxState> e : this.commitStatus.entrySet()) {
                e.getValue().releaseStatePerNode();
            }
        }
    }

    public enum DistributedSubmitState {
        SUCCESS, FAILED, WAITING;
    }

    /**
     * Per-node transaction state. Generally its success or fail. This also keeps the rollback transaction
     */
    private static final class PerNodeTxState {
        private boolean success;
        @Nullable
        private Throwable t;
        private final ReadWriteTransaction rollbackTx;
        DTXLogicalTXProviderType logicalTXProviderType;

        public PerNodeTxState(final boolean success, @Nullable final Throwable t,
                @Nonnull ReadWriteTransaction rollbackTx) {
            this(success, rollbackTx);
            this.t = t;
        }

        public PerNodeTxState(final boolean success, @Nonnull ReadWriteTransaction rollbackTx) {
            this.success = success;
            this.rollbackTx = rollbackTx;
        }

        public boolean isSuccess() {
            return success;
        }

        @Nullable
        public Optional<Throwable> getException() {
            return Optional.fromNullable(t);
        }

        public ReadWriteTransaction getRollbackTx() {
            return rollbackTx;
        }

        public static PerNodeTxState createFailed(final Throwable t,
                final ReadWriteTransaction readWriteTransaction) {
            return new PerNodeTxState(false, t, readWriteTransaction);
        }

        public static PerNodeTxState createSuccess(final ReadWriteTransaction readWriteTransaction) {
            return new PerNodeTxState(true, readWriteTransaction);
        }

        public void releaseStatePerNode() {
            this.rollbackTx.cancel();
        }
    }

    private boolean containsIid(InstanceIdentifier<?> iid) {
        for (DTXLogicalTXProviderType type : this.perNodeTransactionsbyLogicalType.keySet()) {
            if (this.perNodeTransactionsbyLogicalType.get(type).containsKey(iid)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public <T extends DataObject> CheckedFuture<Void, DTxException> mergeAndRollbackOnFailure(
            final LogicalDatastoreType logicalDatastoreType, final InstanceIdentifier<T> instanceIdentifier,
            final T t, final InstanceIdentifier<?> nodeId) {
        return mergeAndRollbackOnFailure(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER, logicalDatastoreType,
                instanceIdentifier, t, nodeId);
    }

    public <T extends DataObject> CheckedFuture<Void, DTxException> putAndRollbackOnFailure(
            final LogicalDatastoreType logicalDatastoreType, final InstanceIdentifier<T> instanceIdentifier,
            final T t, final InstanceIdentifier<?> nodeId) {
        return putAndRollbackOnFailure(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER, logicalDatastoreType,
                instanceIdentifier, t, nodeId);
    }

    public CheckedFuture<Void, DTxException> deleteAndRollbackOnFailure(
            final LogicalDatastoreType logicalDatastoreType, final InstanceIdentifier<?> instanceIdentifier,
            InstanceIdentifier<?> nodeId) {
        return this.deleteAndRollbackOnFailure(DTXLogicalTXProviderType.NETCONF_TX_PROVIDER, logicalDatastoreType,
                instanceIdentifier, nodeId);
    }

    public CheckedFuture<Void, DTxException.RollbackFailedException> rollback() {
        CheckedFuture<Void, DTxException.RollbackFailedException> rollbackFuture = this
                .rollbackUponOperationFailure();
        this.dtxReleaseDevices();
        return rollbackFuture;
    }

    @Override
    public <T extends DataObject> CheckedFuture<Void, DTxException> mergeAndRollbackOnFailure(
            DTXLogicalTXProviderType logicalTXProviderType, LogicalDatastoreType logicalDatastoreType,
            InstanceIdentifier<T> instanceIdentifier, T t, InstanceIdentifier<?> nodeId) {
        Preconditions.checkArgument(containsIid(nodeId), "Unknown node: %s. Not in transaction", nodeId);
        final DTXReadWriteTransaction transaction = this.perNodeTransactionsbyLogicalType.get(logicalTXProviderType)
                .get(nodeId);

        CheckedFuture<Void, DTxException> mergeFuture = transaction.asyncMerge(logicalDatastoreType,
                instanceIdentifier, t);

        final SettableFuture<Void> retFuture = SettableFuture.create();

        Futures.addCallback(mergeFuture, new FutureCallback<Void>() {
            @Override
            public void onSuccess(@Nullable Void aVoid) {
                retFuture.set(null);
            }

            @Override
            public void onFailure(Throwable throwable) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        CheckedFuture<Void, DTxException.RollbackFailedException> rollExcept = rollback();
                        Futures.addCallback(rollExcept, new FutureCallback<Void>() {
                            @Override
                            public void onSuccess(@Nullable Void aVoid) {
                                dtxReleaseDevices();
                                retFuture.setException(new DTxException.EditFailedException(
                                        "Failed to merge but succeed to rollback"));
                            }

                            @Override
                            public void onFailure(Throwable throwable) {
                                dtxReleaseDevices();
                                retFuture.setException(new DTxException.RollbackFailedException(throwable));
                            }
                        });
                    }
                };

                new Thread(runnable).start();
            }
        });

        return Futures.makeChecked(retFuture, new Function<Exception, DTxException>() {
            @Nullable
            @Override
            public DTxException apply(@Nullable Exception e) {
                e = (Exception) e.getCause();
                return e instanceof DTxException ? (DTxException) e
                        : new DTxException("Merge failed and rollback failure", e);
            }
        });
    }

    @Override
    public <T extends DataObject> CheckedFuture<Void, DTxException> putAndRollbackOnFailure(
            DTXLogicalTXProviderType logicalTXProviderType, LogicalDatastoreType logicalDatastoreType,
            InstanceIdentifier<T> instanceIdentifier, T t, InstanceIdentifier<?> nodeId) {
        Preconditions.checkArgument(containsIid(nodeId), "Unknown node: %s. Not in transaction", nodeId);
        final DTXReadWriteTransaction transaction = this.perNodeTransactionsbyLogicalType.get(logicalTXProviderType)
                .get(nodeId);
        Preconditions.checkArgument(containsIid(nodeId), "Unknown node: %s. Not in transaction", nodeId);
        CheckedFuture<Void, DTxException> putFuture = transaction.asyncPut(logicalDatastoreType, instanceIdentifier,
                t);

        final SettableFuture<Void> retFuture = SettableFuture.create();

        Futures.addCallback(putFuture, new FutureCallback<Void>() {
            @Override
            public void onSuccess(@Nullable Void aVoid) {
                retFuture.set(null);
            }

            @Override
            public void onFailure(Throwable throwable) {
                LOG.trace("asyncput failure callback begin to roll back ");
                Runnable rolllbackRoutine = new Runnable() {
                    @Override
                    public void run() {
                        CheckedFuture<Void, DTxException.RollbackFailedException> rollExcept = rollback();

                        Futures.addCallback(rollExcept, new FutureCallback<Void>() {
                            @Override
                            public void onSuccess(@Nullable Void result) {
                                dtxReleaseDevices();
                                retFuture.setException(new DTxException.EditFailedException(
                                        "Failed to put but succeed to rollback"));
                            }

                            @Override
                            public void onFailure(Throwable t) {
                                dtxReleaseDevices();
                                retFuture.setException(new DTxException.RollbackFailedException(t));
                            }
                        });
                    }
                };

                new Thread(rolllbackRoutine).start();
            }
        });

        return Futures.makeChecked(retFuture, new Function<Exception, DTxException>() {
            @Nullable
            @Override
            public DTxException apply(@Nullable Exception e) {
                e = (Exception) e.getCause();
                return e instanceof DTxException ? (DTxException) e
                        : new DTxException("Put failed and rollback failure", e);
            }
        });
    }

    @Override
    public CheckedFuture<Void, DTxException> deleteAndRollbackOnFailure(
            DTXLogicalTXProviderType logicalTXProviderType, LogicalDatastoreType logicalDatastoreType,
            InstanceIdentifier<?> instanceIdentifier, InstanceIdentifier<?> nodeId) {

        Preconditions.checkArgument(containsIid(nodeId), "Unknown node: %s. Not in transaction", nodeId);
        final DTXReadWriteTransaction transaction = this.perNodeTransactionsbyLogicalType.get(logicalTXProviderType)
                .get(nodeId);
        CheckedFuture<Void, DTxException> deleteFuture = transaction.asyncDelete(logicalDatastoreType,
                instanceIdentifier);

        final SettableFuture<Void> retFuture = SettableFuture.create();

        Futures.addCallback(deleteFuture, new FutureCallback<Void>() {
            @Override
            public void onSuccess(@Nullable Void aVoid) {
                retFuture.set(null);
            }

            @Override
            public void onFailure(Throwable throwable) {

                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        CheckedFuture<Void, DTxException.RollbackFailedException> rollExcept = rollback();

                        Futures.addCallback(rollExcept, new FutureCallback<Void>() {
                            @Override
                            public void onSuccess(@Nullable Void aVoid) {
                                dtxReleaseDevices();
                                retFuture.setException(new DTxException.EditFailedException(
                                        "Failed to delete but succeed to rollback"));
                            }

                            @Override
                            public void onFailure(Throwable throwable) {
                                dtxReleaseDevices();
                                retFuture.setException(new DTxException.RollbackFailedException(throwable));
                            }
                        });

                    }
                };
                new Thread(runnable).start();
            }
        });

        return Futures.makeChecked(retFuture, new Function<Exception, DTxException>() {
            @Nullable
            @Override
            public DTxException apply(@Nullable Exception e) {
                e = (Exception) e.getCause();
                return e instanceof DTxException ? (DTxException) e
                        : new DTxException("delete failed and rollback failure", e);
            }
        });
    }
}