BUG-2560 Canonical write to remote netconf devices 38/13538/8
authorMaros Marsalek <mmarsale@cisco.com>
Wed, 10 Dec 2014 15:02:27 +0000 (16:02 +0100)
committerTony Tkacik <ttkacik@cisco.com>
Thu, 8 Jan 2015 09:05:42 +0000 (09:05 +0000)
Current sequence for writing to remote netconf devices:

Writable running only:
- lock running or fail
- edit running (unlock on fail)
- unlock running

Candidate only:
- lock candidate
-- if lock fails, try to discard changes
--- if discard fails, fail
--- if discard succeeds, lock candidate
---- if lock fails, fail
- edit candidate (discard-changes + unlock on fail)
- commit
- unlock candidate

Both writable running and candidate:
- lock running or fail
- SAME AS FOR CANDIDATE ONLY
- unlock running

Change-Id: Ia4e0d43f3131c4072e8505ba72ef09ff441c1fac
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
14 files changed:
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java [new file with mode: 0644]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java [deleted file]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java [moved from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java with 73% similarity]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadWriteTx.java [moved from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadWriteTx.java with 90% similarity]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java [new file with mode: 0644]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java [new file with mode: 0644]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java [new file with mode: 0644]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java [new file with mode: 0644]
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java [new file with mode: 0644]
opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java
opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java

index 09e178f..d5b3778 100644 (file)
@@ -88,6 +88,8 @@ public final class NetconfSessionCapabilities {
                 .add("moduleBasedCapabilities", moduleBasedCaps)
                 .add("rollback", isRollbackSupported())
                 .add("monitoring", isMonitoringSupported())
+                .add("candidate", isCandidateSupported())
+                .add("writableRunning", isRunningWritable())
                 .toString();
     }
 
@@ -99,6 +101,10 @@ public final class NetconfSessionCapabilities {
         return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
     }
 
+    public boolean isRunningWritable() {
+        return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString());
+    }
+
     public boolean isMonitoringSupported() {
         return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)
                 || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
index f3a9acd..aa22e87 100644 (file)
@@ -18,9 +18,12 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
 import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities;
-import org.opendaylight.controller.sal.connect.netconf.sal.tx.NetconfDeviceReadOnlyTx;
-import org.opendaylight.controller.sal.connect.netconf.sal.tx.NetconfDeviceReadWriteTx;
-import org.opendaylight.controller.sal.connect.netconf.sal.tx.NetconfDeviceWriteOnlyTx;
+import org.opendaylight.controller.sal.connect.netconf.sal.tx.ReadOnlyTx;
+import org.opendaylight.controller.sal.connect.netconf.sal.tx.ReadWriteTx;
+import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteCandidateTx;
+import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteCandidateRunningTx;
+import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteRunningTx;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.controller.sal.core.api.RpcImplementation;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
@@ -29,40 +32,48 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 final class NetconfDeviceDataBroker implements DOMDataBroker {
     private final RemoteDeviceId id;
-    private final RpcImplementation rpc;
+    private final NetconfBaseOps netconfOps;
     private final NetconfSessionCapabilities netconfSessionPreferences;
     private final DataNormalizer normalizer;
 
-    public NetconfDeviceDataBroker(final RemoteDeviceId id, final RpcImplementation rpc, final SchemaContext schemaContext, NetconfSessionCapabilities netconfSessionPreferences) {
+    public NetconfDeviceDataBroker(final RemoteDeviceId id, final RpcImplementation rpc, final SchemaContext schemaContext, final NetconfSessionCapabilities netconfSessionPreferences) {
         this.id = id;
-        this.rpc = rpc;
+        this.netconfOps = new NetconfBaseOps(rpc);
         this.netconfSessionPreferences = netconfSessionPreferences;
         normalizer = new DataNormalizer(schemaContext);
     }
 
     @Override
     public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
-        return new NetconfDeviceReadOnlyTx(rpc, normalizer, id);
+        return new ReadOnlyTx(netconfOps, normalizer, id);
     }
 
     @Override
     public DOMDataReadWriteTransaction newReadWriteTransaction() {
-        return new NetconfDeviceReadWriteTx(newReadOnlyTransaction(), newWriteOnlyTransaction());
+        return new ReadWriteTx(newReadOnlyTransaction(), newWriteOnlyTransaction());
     }
 
     @Override
     public DOMDataWriteTransaction newWriteOnlyTransaction() {
-        return new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, netconfSessionPreferences.isCandidateSupported(), netconfSessionPreferences.isRollbackSupported());
+        if(netconfSessionPreferences.isCandidateSupported()) {
+            if(netconfSessionPreferences.isRunningWritable()) {
+                return new WriteCandidateRunningTx(id, netconfOps, normalizer, netconfSessionPreferences);
+            } else {
+                return new WriteCandidateTx(id, netconfOps, normalizer, netconfSessionPreferences);
+            }
+        } else {
+            return new WriteRunningTx(id, netconfOps, normalizer, netconfSessionPreferences);
+        }
     }
 
     @Override
     public ListenerRegistration<DOMDataChangeListener> registerDataChangeListener(final LogicalDatastoreType store, final YangInstanceIdentifier path, final DOMDataChangeListener listener, final DataChangeScope triggeringScope) {
-        throw new UnsupportedOperationException("Data change listeners not supported for netconf mount point");
+        throw new UnsupportedOperationException(id + ": Data change listeners not supported for netconf mount point");
     }
 
     @Override
     public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
-        // TODO implement
-        throw new UnsupportedOperationException("Transaction chains not supported for netconf mount point");
+        throw new UnsupportedOperationException(id + ": Transaction chains not supported for netconf mount point");
     }
+
 }
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java
new file mode 100644 (file)
index 0000000..165d9c4
--- /dev/null
@@ -0,0 +1,146 @@
+package org.opendaylight.controller.sal.connect.netconf.sal.tx;
+
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.createEditConfigStructure;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+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.impl.util.compat.DataNormalizer;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
+import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
+    protected final RemoteDeviceId id;
+    protected final NetconfBaseOps netOps;
+    protected final DataNormalizer normalizer;
+    protected final NetconfSessionCapabilities netconfSessionPreferences;
+    // Allow commit to be called only once
+    protected boolean finished = false;
+
+    public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) {
+        this.netOps = netOps;
+        this.id = id;
+        this.normalizer = normalizer;
+        this.netconfSessionPreferences = netconfSessionPreferences;
+        init();
+    }
+
+    protected void checkNotFinished() {
+        Preconditions.checkState(!isFinished(), "%s: Transaction %s already finished", id, getIdentifier());
+    }
+
+    protected boolean isFinished() {
+        return finished;
+    }
+
+    protected void invokeBlocking(final String msg, final Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>> op) throws NetconfDocumentedException {
+        try {
+            final RpcResult<CompositeNode> compositeNodeRpcResult = op.apply(netOps).get(1L, TimeUnit.MINUTES);
+            if(compositeNodeRpcResult.isSuccessful() == false) {
+                throw new NetconfDocumentedException(id + ": " + msg + " failed: " + compositeNodeRpcResult.getErrors(), NetconfDocumentedException.ErrorType.application,
+                        NetconfDocumentedException.ErrorTag.operation_failed, NetconfDocumentedException.ErrorSeverity.warning);
+            }
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException(e);
+        } catch (final ExecutionException | TimeoutException e) {
+            throw new NetconfDocumentedException(id + ": " + msg + " failed: " + e.getMessage(), e, NetconfDocumentedException.ErrorType.application,
+                    NetconfDocumentedException.ErrorTag.operation_failed, NetconfDocumentedException.ErrorSeverity.warning);
+        }
+    }
+
+    @Override
+    public synchronized boolean cancel() {
+        if(isFinished()) {
+            return false;
+        }
+
+        finished = true;
+        cleanup();
+        return true;
+    }
+
+    protected abstract void init();
+
+    protected abstract void cleanup();
+
+    @Override
+    public Object getIdentifier() {
+        return this;
+    }
+
+    @Override
+    public synchronized void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
+        checkEditable(store);
+
+        try {
+            final YangInstanceIdentifier legacyPath = ReadOnlyTx.toLegacyPath(normalizer, path, id);
+            final CompositeNode legacyData = normalizer.toLegacy(path, data);
+            editConfig(
+                    createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE));
+        } catch (final NetconfDocumentedException e) {
+            handleEditException(path, data, e, "putting");
+        }
+    }
+
+    protected abstract void handleEditException(YangInstanceIdentifier path, NormalizedNode<?, ?> data, NetconfDocumentedException e, String editType);
+    protected abstract void handleDeleteException(YangInstanceIdentifier path, NetconfDocumentedException e);
+
+    @Override
+    public synchronized void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
+        checkEditable(store);
+
+        try {
+            final YangInstanceIdentifier legacyPath = ReadOnlyTx.toLegacyPath(normalizer, path, id);
+            final CompositeNode legacyData = normalizer.toLegacy(path, data);
+            editConfig(
+                    createEditConfigStructure(legacyPath, Optional.<ModifyAction>absent(), Optional.fromNullable(legacyData)), Optional.<ModifyAction>absent());
+        } catch (final NetconfDocumentedException e) {
+            handleEditException(path, data, e, "merge");
+        }
+    }
+
+    @Override
+    public synchronized void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+        checkEditable(store);
+
+        try {
+            editConfig(createEditConfigStructure(
+                    ReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE),
+                    Optional.<CompositeNode>absent()), Optional.of(ModifyAction.NONE));
+        } catch (final NetconfDocumentedException e) {
+            handleDeleteException(path, e);
+        }
+    }
+
+    @Override
+    public final ListenableFuture<RpcResult<TransactionStatus>> commit() {
+        checkNotFinished();
+        finished = true;
+
+        return performCommit();
+    }
+
+    protected abstract ListenableFuture<RpcResult<TransactionStatus>> performCommit();
+
+    private void checkEditable(final LogicalDatastoreType store) {
+        checkNotFinished();
+        Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can edit only configuration data, not %s", store);
+    }
+
+    protected abstract void editConfig(CompositeNode editStructure, Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException;
+}
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java
deleted file mode 100644 (file)
index 4b53dd7..0000000
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (c) 2014 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.controller.sal.connect.netconf.sal.tx;
-
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-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.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicBoolean;
-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.controller.md.sal.common.impl.util.compat.DataNormalizer;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
-import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
-import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.controller.sal.core.api.RpcImplementation;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.RpcError;
-import org.opendaylight.yangtools.yang.common.RpcResult;
-import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
-import org.opendaylight.yangtools.yang.data.api.CompositeNode;
-import org.opendaylight.yangtools.yang.data.api.ModifyAction;
-import org.opendaylight.yangtools.yang.data.api.Node;
-import org.opendaylight.yangtools.yang.data.api.SimpleNode;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
-import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
-import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction, FutureCallback<RpcResult<TransactionStatus>> {
-
-    private static final Logger LOG  = LoggerFactory.getLogger(NetconfDeviceWriteOnlyTx.class);
-
-    private final RemoteDeviceId id;
-    private final RpcImplementation rpc;
-    private final DataNormalizer normalizer;
-
-    private final boolean rollbackSupported;
-    private final boolean candidateSupported;
-    private final CompositeNode targetNode;
-
-    // Allow commit to be called only once
-    private final AtomicBoolean finished = new AtomicBoolean(false);
-
-    public NetconfDeviceWriteOnlyTx(final RemoteDeviceId id, final RpcImplementation rpc, final DataNormalizer normalizer, final boolean candidateSupported, final boolean rollbackOnErrorSupported) {
-        this.id = id;
-        this.rpc = rpc;
-        this.normalizer = normalizer;
-
-        this.candidateSupported = candidateSupported;
-        this.targetNode = getTargetNode(this.candidateSupported);
-        this.rollbackSupported = rollbackOnErrorSupported;
-    }
-
-    @Override
-    public boolean cancel() {
-        if(isFinished()) {
-            return false;
-        }
-
-        return discardChanges();
-    }
-
-    private boolean isFinished() {
-        return finished.get();
-    }
-
-    private boolean discardChanges() {
-        finished.set(true);
-
-        if(candidateSupported) {
-            sendDiscardChanges();
-        }
-        return true;
-    }
-
-    // TODO should the edit operations be blocking ?
-    // TODO should the discard-changes operations be blocking ?
-
-    @Override
-    public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
-        checkNotFinished();
-        Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store);
-
-        try {
-            final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id);
-            final CompositeNode legacyData = normalizer.toLegacy(path, data);
-            sendEditRpc(
-                    createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE));
-        } catch (final ExecutionException e) {
-            LOG.warn("{}: Error putting data to {}, data: {}, discarding changes", id, path, data, e);
-            discardChanges();
-            throw new RuntimeException(id + ": Error while replacing " + path, e);
-        }
-    }
-
-    private void checkNotFinished() {
-        Preconditions.checkState(isFinished() == false, "%s: Transaction %s already finished", id, getIdentifier());
-    }
-
-    @Override
-    public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
-        checkNotFinished();
-        Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store);
-
-        try {
-            final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id);
-            final CompositeNode legacyData = normalizer.toLegacy(path, data);
-            sendEditRpc(
-                    createEditConfigStructure(legacyPath, Optional.<ModifyAction> absent(), Optional.fromNullable(legacyData)), Optional.<ModifyAction> absent());
-        } catch (final ExecutionException e) {
-            LOG.warn("{}: Error merging data to {}, data: {}, discarding changes", id, path, data, e);
-            discardChanges();
-            throw new RuntimeException(id + ": Error while merging " + path, e);
-        }
-    }
-
-    @Override
-    public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-        checkNotFinished();
-        Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store);
-
-        try {
-            sendEditRpc(
-                    createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE), Optional.<CompositeNode>absent()), Optional.of(ModifyAction.NONE));
-        } catch (final ExecutionException e) {
-            LOG.warn("{}: Error deleting data {}, discarding changes", id, path, e);
-            discardChanges();
-            throw new RuntimeException(id + ": Error while deleting " + path, e);
-        }
-    }
-
-    @Override
-    public CheckedFuture<Void, TransactionCommitFailedException> submit() {
-        final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
-            @Override
-            public Void apply(final RpcResult<TransactionStatus> input) {
-                return null;
-            }
-        });
-
-        return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
-            @Override
-            public TransactionCommitFailedException apply(final Exception input) {
-                return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
-            }
-        });
-    }
-
-    @Override
-    public ListenableFuture<RpcResult<TransactionStatus>> commit() {
-        checkNotFinished();
-        finished.set(true);
-
-        if(candidateSupported == false) {
-            return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build());
-        }
-
-        final ListenableFuture<RpcResult<CompositeNode>> rpcResult = rpc.invokeRpc(
-                NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
-
-        final ListenableFuture<RpcResult<TransactionStatus>> transformed = Futures.transform(rpcResult,
-                new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
-                    @Override
-                    public RpcResult<TransactionStatus> apply(final RpcResult<CompositeNode> input) {
-                        if (input.isSuccessful()) {
-                            return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
-                        } else {
-                            final RpcResultBuilder<TransactionStatus> failed = RpcResultBuilder.failed();
-                            for (final RpcError rpcError : input.getErrors()) {
-                                failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(),
-                                        rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause());
-                            }
-                            return failed.build();
-                        }
-                    }
-                });
-
-        Futures.addCallback(transformed, this);
-        return transformed;
-    }
-
-    @Override
-    public void onSuccess(final RpcResult<TransactionStatus> result) {
-        LOG.debug("{}: Write successful, transaction: {}", id, getIdentifier());
-    }
-
-    @Override
-    public void onFailure(final Throwable t) {
-        LOG.warn("{}: Write failed, transaction {}, discarding changes", id, getIdentifier(), t);
-        discardChanges();
-    }
-
-    private void sendEditRpc(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) throws ExecutionException {
-        final CompositeNode editConfigRequest = createEditConfigRequest(editStructure, defaultOperation);
-        final RpcResult<CompositeNode> rpcResult;
-        try {
-            rpcResult = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, editConfigRequest).get();
-        } catch (final InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new RuntimeException(id + ": Interrupted while waiting for response", e);
-        }
-
-        // Check result
-        if(rpcResult.isSuccessful() == false) {
-            throw new ExecutionException(
-                    String.format("%s: Pre-commit rpc failed, request: %s, errors: %s", id, editConfigRequest, rpcResult.getErrors()), null);
-        }
-    }
-
-    private void sendDiscardChanges() {
-        final ListenableFuture<RpcResult<CompositeNode>> discardFuture = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
-        Futures.addCallback(discardFuture, new FutureCallback<RpcResult<CompositeNode>>() {
-            @Override
-            public void onSuccess(final RpcResult<CompositeNode> result) {
-                LOG.debug("{}: Discarding transaction: {}", id, getIdentifier());
-            }
-
-            @Override
-            public void onFailure(final Throwable t) {
-                LOG.error("{}: Discarding changes failed, transaction: {}. Device configuration might be corrupted", id, getIdentifier(), t);
-                throw new RuntimeException(id + ": Discarding changes failed, transaction " + getIdentifier(), t);
-            }
-        });
-    }
-
-    private CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation,
-                                                    final Optional<CompositeNode> lastChildOverride) {
-        Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath);
-
-        // Create deepest edit element with expected edit operation
-        CompositeNode previous = getDeepestEditElement(dataPath.getLastPathArgument(), operation, lastChildOverride);
-
-        Iterator<PathArgument> it = dataPath.getReversePathArguments().iterator();
-        // Remove already processed deepest child
-        it.next();
-
-        // Create edit structure in reversed order
-        while (it.hasNext()) {
-            final YangInstanceIdentifier.PathArgument arg = it.next();
-            final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
-            builder.setQName(arg.getNodeType());
-
-            addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
-
-            builder.add(previous);
-            previous = builder.toInstance();
-        }
-        return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
-    }
-
-    private void addPredicatesToCompositeNodeBuilder(final Map<QName, Object> predicates, final CompositeNodeBuilder<ImmutableCompositeNode> builder) {
-        for (final Map.Entry<QName, Object> entry : predicates.entrySet()) {
-            builder.addLeaf(entry.getKey(), entry.getValue());
-        }
-    }
-
-    private Map<QName, Object> getPredicates(final YangInstanceIdentifier.PathArgument arg) {
-        Map<QName, Object> predicates = Collections.emptyMap();
-        if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
-            predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues();
-        }
-        return predicates;
-    }
-
-    private CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional<ModifyAction> operation, final Optional<CompositeNode> lastChildOverride) {
-        final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
-        builder.setQName(arg.getNodeType());
-
-        final Map<QName, Object> predicates = getPredicates(arg);
-        addPredicatesToCompositeNodeBuilder(predicates, builder);
-
-        if (operation.isPresent()) {
-            builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
-        }
-        if (lastChildOverride.isPresent()) {
-            final List<Node<?>> children = lastChildOverride.get().getValue();
-            for(final Node<?> child : children) {
-                if(!predicates.containsKey(child.getKey())) {
-                    builder.add(child);
-                }
-            }
-        }
-
-        return builder.toInstance();
-    }
-
-    private CompositeNode createEditConfigRequest(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) {
-        final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
-
-        // Target
-        final Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
-        ret.add(targetWrapperNode);
-
-        // Default operation
-        if(defaultOperation.isPresent()) {
-            final SimpleNode<String> defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, modifyOperationToXmlString(defaultOperation.get()));
-            ret.add(defOp);
-        }
-
-        // Error option
-        if(rollbackSupported) {
-            ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
-        }
-
-        ret.setQName(NETCONF_EDIT_CONFIG_QNAME);
-        // Edit content
-        ret.add(editStructure);
-        return ret.toInstance();
-    }
-
-    private String modifyOperationToXmlString(final ModifyAction operation) {
-        return operation.name().toLowerCase();
-    }
-
-    public CompositeNode getTargetNode(final boolean candidateSupported) {
-        if(candidateSupported) {
-            return ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.<Node<?>>of());
-        } else {
-            return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
-        }
-    }
-
-    @Override
-    public Object getIdentifier() {
-        return this;
-    }
-}
@@ -7,20 +7,24 @@
  */
 package org.opendaylight.controller.sal.connect.netconf.sal.tx;
 
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_QNAME;
+
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 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.concurrent.ExecutionException;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.controller.sal.core.api.RpcImplementation;
 import org.opendaylight.yangtools.util.concurrent.MappingCheckedFuture;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
@@ -29,35 +33,44 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.concurrent.ExecutionException;
-
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.CONFIG_SOURCE_RUNNING;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
-import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure;
-
 
-public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction {
+public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
 
-    private static final Logger LOG  = LoggerFactory.getLogger(NetconfDeviceReadOnlyTx.class);
+    private static final Logger LOG  = LoggerFactory.getLogger(ReadOnlyTx.class);
 
-    private final RpcImplementation rpc;
+    private final NetconfBaseOps netconfOps;
     private final DataNormalizer normalizer;
     private final RemoteDeviceId id;
+    private final FutureCallback<RpcResult<CompositeNode>> loggingCallback;
 
-    public NetconfDeviceReadOnlyTx(final RpcImplementation rpc, final DataNormalizer normalizer, final RemoteDeviceId id) {
-        this.rpc = rpc;
+    public ReadOnlyTx(final NetconfBaseOps netconfOps, final DataNormalizer normalizer, final RemoteDeviceId id) {
+        this.netconfOps = netconfOps;
         this.normalizer = normalizer;
         this.id = id;
+        // Simple logging callback to log result of read operation
+        loggingCallback = new FutureCallback<RpcResult<CompositeNode>>() {
+            @Override
+            public void onSuccess(final RpcResult<CompositeNode> result) {
+                if(result.isSuccessful()) {
+                    LOG.trace("{}: Reading data successful", id);
+                } else {
+                    LOG.warn("{}: Reading data unsuccessful: {}", id, result.getErrors());
+                }
+
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                LOG.warn("{}: Reading data failed", id, t);
+            }
+        };
     }
 
     private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readConfigurationData(
             final YangInstanceIdentifier path) {
-        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME,
-                NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, CONFIG_SOURCE_RUNNING, toFilterStructure(path)));
-
-        final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(future, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
+        final ListenableFuture<RpcResult<CompositeNode>> configRunning = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path));
+        // Find data node and normalize its content
+        final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configRunning, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
             @Override
             public Optional<NormalizedNode<?, ?>> apply(final RpcResult<CompositeNode> result) {
                 checkReadSuccess(result, path);
@@ -77,7 +90,7 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction
     private void checkReadSuccess(final RpcResult<CompositeNode> result, final YangInstanceIdentifier path) {
         try {
             Preconditions.checkArgument(result.isSuccessful(), "%s: Unable to read data: %s, errors: %s", id, path, result.getErrors());
-        } catch (IllegalArgumentException e) {
+        } catch (final IllegalArgumentException e) {
             LOG.warn("{}: Unable to read data: {}, errors: {}", id, path, result.getErrors());
             throw e;
         }
@@ -97,9 +110,10 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction
 
     private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readOperationalData(
             final YangInstanceIdentifier path) {
-        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, toFilterStructure(path)));
+        final ListenableFuture<RpcResult<CompositeNode>> configCandidate = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path));
 
-        final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(future, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
+        // Find data node and normalize its content
+        final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configCandidate, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
             @Override
             public Optional<NormalizedNode<?, ?>> apply(final RpcResult<CompositeNode> result) {
                 checkReadSuccess(result, path);
@@ -138,10 +152,9 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction
         throw new IllegalArgumentException(String.format("%s, Cannot read data %s for %s datastore, unknown datastore type", id, path, store));
     }
 
-    @Override public CheckedFuture<Boolean, ReadFailedException> exists(
-        LogicalDatastoreType store,
-        YangInstanceIdentifier path) {
-        CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
+    @Override
+    public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
             data = read(store, path);
 
         try {
@@ -26,12 +26,12 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
 import java.util.concurrent.ExecutionException;
 
-public class NetconfDeviceReadWriteTx implements DOMDataReadWriteTransaction {
+public class ReadWriteTx implements DOMDataReadWriteTransaction {
 
     private final DOMDataReadTransaction delegateReadTx;
     private final DOMDataWriteTransaction delegateWriteTx;
 
-    public NetconfDeviceReadWriteTx(final DOMDataReadTransaction delegateReadTx, final DOMDataWriteTransaction delegateWriteTx) {
+    public ReadWriteTx(final DOMDataReadTransaction delegateReadTx, final DOMDataWriteTransaction delegateWriteTx) {
         this.delegateReadTx = delegateReadTx;
         this.delegateWriteTx = delegateWriteTx;
     }
@@ -73,9 +73,9 @@ public class NetconfDeviceReadWriteTx implements DOMDataReadWriteTransaction {
     }
 
     @Override public CheckedFuture<Boolean, ReadFailedException> exists(
-        LogicalDatastoreType store,
-        YangInstanceIdentifier path) {
-        CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
+        final LogicalDatastoreType store,
+        final YangInstanceIdentifier path) {
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
             data = read(store, path);
 
         try {
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java
new file mode 100644 (file)
index 0000000..4a9a939
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2014 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.controller.sal.connect.netconf.sal.tx;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback;
+import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tx implementation for netconf devices that support only candidate datastore and writable running
+ * The sequence goes exactly as with only candidate supported, with one addition:
+ * <ul>
+ *     <li>Running datastore is locked as the first thing and this lock has to succeed</li>
+ * </ul>
+ */
+public class WriteCandidateRunningTx extends WriteCandidateTx {
+
+    private static final Logger LOG  = LoggerFactory.getLogger(WriteCandidateRunningTx.class);
+
+    public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) {
+        super(id, netOps, normalizer, netconfSessionPreferences);
+    }
+
+    @Override
+    protected synchronized void init() {
+        lockRunning();
+        super.init();
+    }
+
+    @Override
+    protected void cleanupOnSuccess() {
+        super.cleanupOnSuccess();
+        unlockRunning();
+    }
+
+    private void lockRunning() {
+        try {
+            invokeBlocking("Lock running", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+                @Override
+                public ListenableFuture<RpcResult<CompositeNode>> apply(final NetconfBaseOps input) {
+                    return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
+                }
+            });
+        } catch (final NetconfDocumentedException e) {
+            LOG.warn("{}: Failed to lock running. Failed to initialize transaction", e);
+            finished = true;
+            throw new RuntimeException(id + ": Failed to lock running. Failed to initialize transaction", e);
+        }
+    }
+
+    /**
+     * This has to be non blocking since it is called from a callback on commit and its netty threadpool that is really sensitive to blocking calls
+     */
+    private void unlockRunning() {
+        netOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
+    }
+}
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java
new file mode 100644 (file)
index 0000000..0ea6298
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2014 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.controller.sal.connect.netconf.sal.tx;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback;
+import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tx implementation for netconf devices that support only candidate datastore and no writable running
+ * The sequence goes as:
+ * <ol>
+ * <li/> Lock candidate datastore on tx construction
+ *  <ul>
+ * <li/> Lock has to succeed, if it does not, an attempt to discard changes is made
+ * <li/> Discard changes has to succeed
+ * <li/> If discard is successful, lock is reattempted
+ * <li/> Second lock attempt has to succeed
+ * </ul>
+ * <li/> Edit-config in candidate N times
+ * <ul>
+ * <li/> If any issue occurs during edit, datastore is discarded using discard-changes rpc, unlocked and an exception is thrown async
+ * </ul>
+ * <li/> Commit and Unlock candidate datastore async
+ * </ol>
+ */
+public class WriteCandidateTx extends AbstractWriteTx {
+
+    private static final Logger LOG  = LoggerFactory.getLogger(WriteCandidateTx.class);
+
+    private static final Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>> RPC_RESULT_TO_TX_STATUS = new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
+        @Override
+        public RpcResult<TransactionStatus> apply(final RpcResult<CompositeNode> input) {
+            if (input.isSuccessful()) {
+                return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
+            } else {
+                final RpcResultBuilder<TransactionStatus> failed = RpcResultBuilder.failed();
+                for (final RpcError rpcError : input.getErrors()) {
+                    failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(),
+                            rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause());
+                }
+                return failed.build();
+            }
+        }
+    };
+
+    public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) {
+        super(rpc, id, normalizer, netconfSessionPreferences);
+    }
+
+    @Override
+    protected synchronized void init() {
+        LOG.trace("{}: Initializing {} transaction", id, getClass().getSimpleName());
+
+        try {
+            lock();
+        } catch (final NetconfDocumentedException e) {
+            try {
+                LOG.warn("{}: Failed to lock candidate, attempting discard changes", id);
+                discardChanges();
+                LOG.warn("{}: Changes discarded successfully, attempting lock", id);
+                lock();
+            } catch (final NetconfDocumentedException secondE) {
+                LOG.error("{}: Failed to prepare candidate. Failed to initialize transaction", id, secondE);
+                throw new RuntimeException(id + ": Failed to prepare candidate. Failed to initialize transaction", secondE);
+            }
+        }
+    }
+
+    private void lock() throws NetconfDocumentedException {
+        try {
+            invokeBlocking("Lock candidate", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+                @Override
+                public ListenableFuture<RpcResult<CompositeNode>> apply(final NetconfBaseOps input) {
+                    return input.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id));
+                }
+            });
+        } catch (final NetconfDocumentedException e) {
+            LOG.warn("{}: Failed to lock candidate", id, e);
+            throw e;
+        }
+    }
+
+    @Override
+    protected void cleanup() {
+        discardChanges();
+        cleanupOnSuccess();
+    }
+
+    @Override
+    protected void handleEditException(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data, final NetconfDocumentedException e, final String editType) {
+        LOG.warn("{}: Error " + editType + " data to (candidate){}, data: {}, canceling", id, path, data, e);
+        cancel();
+        throw new RuntimeException(id + ": Error while " + editType + ": (candidate)" + path, e);
+    }
+
+    @Override
+    protected void handleDeleteException(final YangInstanceIdentifier path, final NetconfDocumentedException e) {
+        LOG.warn("{}: Error deleting data (candidate){}, canceling", id, path, e);
+        cancel();
+        throw new RuntimeException(id + ": Error while deleting (candidate)" + path, e);
+    }
+
+    @Override
+    public synchronized CheckedFuture<Void, TransactionCommitFailedException> submit() {
+        final ListenableFuture<Void> commitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
+            @Override
+            public Void apply(final RpcResult<TransactionStatus> input) {
+                return null;
+            }
+        });
+
+        return Futures.makeChecked(commitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
+            @Override
+            public TransactionCommitFailedException apply(final Exception input) {
+                return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
+            }
+        });
+    }
+
+    /**
+     * This has to be non blocking since it is called from a callback on commit and its netty threadpool that is really sensitive to blocking calls
+     */
+    private void discardChanges() {
+        netOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id));
+    }
+
+    @Override
+    public synchronized ListenableFuture<RpcResult<TransactionStatus>> performCommit() {
+        final ListenableFuture<RpcResult<CompositeNode>> rpcResult = netOps.commit(new NetconfRpcFutureCallback("Commit", id) {
+            @Override
+            public void onSuccess(final RpcResult<CompositeNode> result) {
+                super.onSuccess(result);
+                LOG.debug("{}: Write successful, transaction: {}. Unlocking", id, getIdentifier());
+                cleanupOnSuccess();
+            }
+
+            @Override
+            protected void onUnsuccess(final RpcResult<CompositeNode> result) {
+                LOG.error("{}: Write failed, transaction {}, discarding changes, unlocking: {}", id, getIdentifier(), result.getErrors());
+                cleanup();
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                LOG.error("{}: Write failed, transaction {}, discarding changes, unlocking", id, getIdentifier(), t);
+                cleanup();
+            }
+        });
+
+        return Futures.transform(rpcResult, RPC_RESULT_TO_TX_STATUS);
+    }
+
+    protected void cleanupOnSuccess() {
+        unlock();
+    }
+
+    @Override
+    protected void editConfig(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
+        invokeBlocking("Edit candidate", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+            @Override
+            public ListenableFuture<RpcResult<CompositeNode>> apply(final NetconfBaseOps input) {
+                        return defaultOperation.isPresent()
+                                ? input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, defaultOperation.get(),
+                                netconfSessionPreferences.isRollbackSupported())
+                                : input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure,
+                                netconfSessionPreferences.isRollbackSupported());
+            }
+        });
+    }
+
+    /**
+     * This has to be non blocking since it is called from a callback on commit and its netty threadpool that is really sensitive to blocking calls
+     */
+    private void unlock() {
+        netOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id));
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java
new file mode 100644 (file)
index 0000000..28173b1
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2014 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.controller.sal.connect.netconf.sal.tx;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback;
+import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tx implementation for netconf devices that support only writable-running with no candidate
+ * The sequence goes as:
+ * <ol>
+ * <li/> Lock running datastore on tx construction
+ * <ul>
+ * <li/> Lock has to succeed, if it does not, transaction is failed
+ * </ul>
+ * <li/> Edit-config in running N times
+ * <ul>
+ * <li/> If any issue occurs during edit, datastore is unlocked and an exception is thrown
+ * </ul>
+ * <li/> Unlock running datastore on tx commit
+ * </ol>
+ */
+public class WriteRunningTx extends AbstractWriteTx {
+
+    private static final Logger LOG  = LoggerFactory.getLogger(WriteRunningTx.class);
+
+    public WriteRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps,
+                          final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) {
+        super(netOps, id, normalizer, netconfSessionPreferences);
+    }
+
+    @Override
+    protected synchronized void init() {
+        lock();
+    }
+
+    private void lock() {
+        try {
+            invokeBlocking("Lock running", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+                @Override
+                public ListenableFuture<RpcResult<CompositeNode>> apply(final NetconfBaseOps input) {
+                    return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
+                }
+            });
+        } catch (final NetconfDocumentedException e) {
+            LOG.warn("{}: Failed to initialize netconf transaction (lock running)", e);
+            finished = true;
+            throw new RuntimeException(id + ": Failed to initialize netconf transaction (lock running)", e);
+        }
+    }
+
+    @Override
+    protected void cleanup() {
+        unlock();
+    }
+
+    @Override
+    protected void handleEditException(final YangInstanceIdentifier path, final NormalizedNode<?, ?> data, final NetconfDocumentedException e, final String editType) {
+        LOG.warn("{}: Error " + editType + " data to (running){}, data: {}, canceling", id, path, data, e);
+        cancel();
+        throw new RuntimeException(id + ": Error while " + editType + ": (running)" + path, e);
+    }
+
+    @Override
+    protected void handleDeleteException(final YangInstanceIdentifier path, final NetconfDocumentedException e) {
+        LOG.warn("{}: Error deleting data (running){}, canceling", id, path, e);
+        cancel();
+        throw new RuntimeException(id + ": Error while deleting (running)" + path, e);
+    }
+
+    @Override
+    public synchronized CheckedFuture<Void, TransactionCommitFailedException> submit() {
+        final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
+            @Override
+            public Void apply(final RpcResult<TransactionStatus> input) {
+                return null;
+            }
+        });
+
+        return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
+            @Override
+            public TransactionCommitFailedException apply(final Exception input) {
+                return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
+            }
+        });
+    }
+
+    @Override
+    public synchronized ListenableFuture<RpcResult<TransactionStatus>> performCommit() {
+        unlock();
+        return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build());
+    }
+
+    @Override
+    protected void editConfig(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
+        invokeBlocking("Edit running", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+            @Override
+            public ListenableFuture<RpcResult<CompositeNode>> apply(final NetconfBaseOps input) {
+                        return defaultOperation.isPresent()
+                                ? input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, defaultOperation.get(),
+                                netconfSessionPreferences.isRollbackSupported())
+                                : input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure,
+                                netconfSessionPreferences.isRollbackSupported());
+            }
+        });
+    }
+
+    private void unlock() {
+        try {
+            invokeBlocking("Unlocking running", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+                @Override
+                public ListenableFuture<RpcResult<CompositeNode>> apply(final NetconfBaseOps input) {
+                    return input.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
+                }
+            });
+        } catch (final NetconfDocumentedException e) {
+            LOG.warn("{}: Failed to unlock running datastore", e);
+            throw new RuntimeException(id + ": Failed to unlock running datastore", e);
+        }
+    }
+}
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java
new file mode 100644 (file)
index 0000000..8ac8a48
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2014 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.controller.sal.connect.netconf.util;
+
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_SOURCE_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Collections;
+import org.opendaylight.controller.sal.core.api.RpcImplementation;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.Node;
+import org.opendaylight.yangtools.yang.data.api.SimpleNode;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
+import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl;
+import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
+
+/**
+ * Provides base operations for netconf e.g. get, get-config, edit-config, (un)lock, commit etc.
+ * According to RFC-6241
+ */
+public final class NetconfBaseOps {
+
+    private final RpcImplementation rpc;
+
+    public NetconfBaseOps(final RpcImplementation rpc) {
+        this.rpc = rpc;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> lock(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(datastore);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(datastore));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> lockCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(NETCONF_CANDIDATE_QNAME));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+
+    public ListenableFuture<RpcResult<CompositeNode>> lockRunning(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(NETCONF_RUNNING_QNAME));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> unlock(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(datastore);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(datastore));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> unlockRunning(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(NETCONF_RUNNING_QNAME));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> unlockCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(NETCONF_CANDIDATE_QNAME));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> discardChanges(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        Preconditions.checkNotNull(callback);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> commit(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        Preconditions.checkNotNull(callback);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> validate(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(datastore);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME, getValidateContent(datastore));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> validateCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        return validate(callback, NETCONF_CANDIDATE_QNAME);
+    }
+
+
+    public ListenableFuture<RpcResult<CompositeNode>> validateRunning(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        return validate(callback, NETCONF_RUNNING_QNAME);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> copyConfig(final FutureCallback<RpcResult<CompositeNode>> callback, final QName source, final QName target) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(source);
+        Preconditions.checkNotNull(target);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME, getCopyConfigContent(source, target));
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> copyRunningToCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+        return copyConfig(callback, NETCONF_RUNNING_QNAME, NETCONF_CANDIDATE_QNAME);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> getConfig(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore, final Optional<YangInstanceIdentifier> filterPath) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(datastore);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future;
+        if (filterPath.isPresent()) {
+            final Node<?> node = toFilterStructure(filterPath.get());
+            future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME,
+                            NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, getSourceNode(datastore), node));
+        } else {
+            future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME,
+                            NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, getSourceNode(datastore)));
+        }
+
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> getConfigRunning(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+        return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> getConfigCandidate(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+        return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> get(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore, final Optional<YangInstanceIdentifier> filterPath) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(datastore);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future;
+        if (filterPath.isPresent()) {
+            final Node<?> node = toFilterStructure(filterPath.get());
+            future = rpc.invokeRpc(NETCONF_GET_QNAME,
+                            NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, getSourceNode(datastore), node));
+        } else {
+            future = rpc.invokeRpc(NETCONF_GET_QNAME,
+                            NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, getSourceNode(datastore)));
+        }
+
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> getRunning(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+        return get(callback, NETCONF_RUNNING_QNAME, filterPath);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> getCandidate(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+        return get(callback, NETCONF_CANDIDATE_QNAME, filterPath);
+    }
+
+
+    public ListenableFuture<RpcResult<CompositeNode>> editConfigCandidate(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) {
+        return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.of(modifyAction), rollback);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> editConfigCandidate(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final boolean rollback) {
+        return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.<ModifyAction>absent(), rollback);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> editConfigRunning(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) {
+        return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.of(modifyAction), rollback);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> editConfigRunning(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final boolean rollback) {
+        return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.<ModifyAction>absent(), rollback);
+    }
+
+    public ListenableFuture<RpcResult<CompositeNode>> editConfig(final FutureCallback<? super RpcResult<CompositeNode>> callback, final QName datastore, final CompositeNode editStructure, final Optional<ModifyAction> modifyAction, final boolean rollback) {
+        Preconditions.checkNotNull(editStructure);
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(datastore);
+
+        final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, getEditConfigContent(datastore, editStructure, modifyAction, rollback));
+
+        Futures.addCallback(future, callback);
+        return future;
+    }
+
+    private CompositeNode getEditConfigContent(final QName datastore, final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation, final boolean rollback) {
+        final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
+
+        // Target
+        ret.add(getTargetNode(datastore));
+
+        // Default operation
+        if(defaultOperation.isPresent()) {
+            final SimpleNode<String> defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, NetconfMessageTransformUtil.modifyOperationToXmlString(defaultOperation.get()));
+            ret.add(defOp);
+        }
+
+        // Error option
+        if(rollback) {
+            ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
+        }
+
+        ret.setQName(NETCONF_EDIT_CONFIG_QNAME);
+        // Edit content
+        ret.add(editStructure);
+        return ret.toInstance();
+    }
+
+    private static CompositeNode getSourceNode(final QName datastore) {
+        return NodeFactory.createImmutableCompositeNode(NETCONF_SOURCE_QNAME, null,
+                Collections.<Node<?>> singletonList(new SimpleNodeTOImpl<>(datastore, null, null)));
+    }
+
+
+    public static CompositeNode getLockContent(final QName datastore) {
+        return NodeFactory.createImmutableCompositeNode(NETCONF_LOCK_QNAME, null, Collections.<Node<?>>singletonList(
+                getTargetNode(datastore)));
+    }
+
+    private static CompositeNode getTargetNode(final QName datastore) {
+        return NodeFactory.createImmutableCompositeNode(NETCONF_TARGET_QNAME, null, Collections.<Node<?>>singletonList(
+                NodeFactory.createImmutableSimpleNode(datastore, null, null)
+        ));
+    }
+
+    public static CompositeNode getCopyConfigContent(final QName source, final QName target) {
+        return NodeFactory.createImmutableCompositeNode(NETCONF_LOCK_QNAME, null,
+                Lists.<Node<?>> newArrayList(getTargetNode(target), getSourceNode(source)));
+    }
+
+    public static CompositeNode getValidateContent(final QName source) {
+        return NodeFactory.createImmutableCompositeNode(NETCONF_VALIDATE_QNAME, null, Lists.<Node<?>> newArrayList(getSourceNode(source)));
+    }
+
+    public static CompositeNode getUnLockContent(final QName preferedDatastore) {
+        return NodeFactory.createImmutableCompositeNode(NETCONF_UNLOCK_QNAME, null, Collections.<Node<?>>singletonList(
+                getTargetNode(preferedDatastore)));
+    }
+
+}
index e3a7441..9eba241 100644 (file)
@@ -14,6 +14,7 @@ import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.net.URI;
 import java.util.ArrayList;
@@ -31,6 +32,7 @@ import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
 import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -59,36 +61,42 @@ public class NetconfMessageTransformUtil {
     public static final QName IETF_NETCONF_MONITORING_SCHEMA_VERSION = QName.create(IETF_NETCONF_MONITORING, "version");
     public static final QName IETF_NETCONF_MONITORING_SCHEMA_NAMESPACE = QName.create(IETF_NETCONF_MONITORING, "namespace");
 
-    public static final URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
-    public static final QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf");
-    public static final QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data");
-    public static final QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply");
-    public static final QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option");
-    public static final QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running");
-    static final List<Node<?>> RUNNING = Collections.<Node<?>> singletonList(new SimpleNodeTOImpl<>(NETCONF_RUNNING_QNAME, null, null));
-    public static final QName NETCONF_SOURCE_QNAME = QName.create(NETCONF_QNAME, "source");
-    public static final CompositeNode CONFIG_SOURCE_RUNNING = new CompositeNodeTOImpl(NETCONF_SOURCE_QNAME, null, RUNNING);
-    public static final QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate");
-    public static final QName NETCONF_TARGET_QNAME = QName.create(NETCONF_QNAME, "target");
-    public static final QName NETCONF_CONFIG_QNAME = QName.create(NETCONF_QNAME, "config");
-    public static final QName NETCONF_COMMIT_QNAME = QName.create(NETCONF_QNAME, "commit");
-    public static final QName NETCONF_OPERATION_QNAME = QName.create(NETCONF_QNAME, "operation");
-    public static final QName NETCONF_DEFAULT_OPERATION_QNAME = QName.create(NETCONF_OPERATION_QNAME, "default-operation");
-    public static final QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config");
-    public static final QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config");
-    public static final QName NETCONF_DISCARD_CHANGES_QNAME = QName.create(NETCONF_QNAME, "discard-changes");
-    public static final QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type");
-    public static final QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter");
-    public static final QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get");
-    public static final QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc");
-
-    public static final URI NETCONF_ROLLBACK_ON_ERROR_URI = URI
+    public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
+    public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf");
+    public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data");
+    public static QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply");
+    public static QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option");
+    public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running");
+    public static QName NETCONF_SOURCE_QNAME = QName.create(NETCONF_QNAME, "source");
+    public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate");
+    public static QName NETCONF_TARGET_QNAME = QName.create(NETCONF_QNAME, "target");
+    public static QName NETCONF_CONFIG_QNAME = QName.create(NETCONF_QNAME, "config");
+    public static QName NETCONF_COMMIT_QNAME = QName.create(NETCONF_QNAME, "commit");
+    public static QName NETCONF_VALIDATE_QNAME = QName.create(NETCONF_QNAME, "validate");
+    public static QName NETCONF_COPY_CONFIG_QNAME = QName.create(NETCONF_QNAME, "copy-config");
+    public static QName NETCONF_OPERATION_QNAME = QName.create(NETCONF_QNAME, "operation");
+    public static QName NETCONF_DEFAULT_OPERATION_QNAME = QName.create(NETCONF_OPERATION_QNAME, "default-operation");
+    public static QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config");
+    public static QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config");
+    public static QName NETCONF_DISCARD_CHANGES_QNAME = QName.create(NETCONF_QNAME, "discard-changes");
+    public static QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type");
+    public static QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter");
+    public static QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get");
+    public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc");
+
+    public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI
             .create("urn:ietf:params:netconf:capability:rollback-on-error:1.0");
-    public static final String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error";
+    public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error";
 
-    public static final URI NETCONF_CANDIDATE_URI = URI
+    public static URI NETCONF_CANDIDATE_URI = URI
             .create("urn:ietf:params:netconf:capability:candidate:1.0");
 
+    public static URI NETCONF_RUNNING_WRITABLE_URI = URI
+            .create("urn:ietf:params:netconf:capability:writable-running:1.0");
+
+    public static QName NETCONF_LOCK_QNAME = QName.create(NETCONF_QNAME, "lock");
+    public static QName NETCONF_UNLOCK_QNAME = QName.create(NETCONF_QNAME, "unlock");
+
     // Discard changes message
     public static final CompositeNode DISCARD_CHANGES_RPC_CONTENT =
             NodeFactory.createImmutableCompositeNode(NETCONF_DISCARD_CHANGES_QNAME, null, Collections.<Node<?>>emptyList());
@@ -116,7 +124,7 @@ public class NetconfMessageTransformUtil {
     static Node<?> toNode(final YangInstanceIdentifier.NodeIdentifierWithPredicates argument, final Node<?> node) {
         final List<Node<?>> list = new ArrayList<>();
         for (final Map.Entry<QName, Object> arg : argument.getKeyValues().entrySet()) {
-            list.add(new SimpleNodeTOImpl<>(arg.getKey(), null, arg.getValue()));
+            list.add(new SimpleNodeTOImpl(arg.getKey(), null, arg.getValue()));
         }
         if (node != null) {
             list.add(node);
@@ -411,4 +419,71 @@ public class NetconfMessageTransformUtil {
         }
         return current;
     }
+
+    public static String modifyOperationToXmlString(final ModifyAction operation) {
+        return operation.name().toLowerCase();
+    }
+
+
+    public static CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation,
+                                                    final Optional<CompositeNode> lastChildOverride) {
+        Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath);
+
+        List<YangInstanceIdentifier.PathArgument> reversedPath = Lists.reverse(dataPath.getPath());
+
+        // Create deepest edit element with expected edit operation
+        CompositeNode previous = getDeepestEditElement(reversedPath.get(0), operation, lastChildOverride);
+
+        // Remove already processed deepest child
+        reversedPath = Lists.newArrayList(reversedPath);
+        reversedPath.remove(0);
+
+        // Create edit structure in reversed order
+        for (final YangInstanceIdentifier.PathArgument arg : reversedPath) {
+            final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
+            builder.setQName(arg.getNodeType());
+
+            addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
+
+            builder.add(previous);
+            previous = builder.toInstance();
+        }
+        return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
+    }
+
+    public static void addPredicatesToCompositeNodeBuilder(final Map<QName, Object> predicates, final CompositeNodeBuilder<ImmutableCompositeNode> builder) {
+        for (final Map.Entry<QName, Object> entry : predicates.entrySet()) {
+            builder.addLeaf(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public static Map<QName, Object> getPredicates(final YangInstanceIdentifier.PathArgument arg) {
+        Map<QName, Object> predicates = Collections.emptyMap();
+        if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
+            predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues();
+        }
+        return predicates;
+    }
+
+    public static CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional<ModifyAction> operation, final Optional<CompositeNode> lastChildOverride) {
+        final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
+        builder.setQName(arg.getNodeType());
+
+        final Map<QName, Object> predicates = getPredicates(arg);
+        addPredicatesToCompositeNodeBuilder(predicates, builder);
+
+        if (operation.isPresent()) {
+            builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
+        }
+        if (lastChildOverride.isPresent()) {
+            final List<Node<?>> children = lastChildOverride.get().getValue();
+            for(final Node<?> child : children) {
+                if(!predicates.containsKey(child.getKey())) {
+                    builder.add(child);
+                }
+            }
+        }
+
+        return builder.toInstance();
+    }
 }
diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java
new file mode 100644 (file)
index 0000000..6e1e9d7
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014 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.controller.sal.connect.netconf.util;
+
+import com.google.common.util.concurrent.FutureCallback;
+import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteRunningTx;
+import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple Netconf rpc logging callback
+ */
+public class NetconfRpcFutureCallback implements FutureCallback<RpcResult<CompositeNode>> {
+    private static final Logger LOG  = LoggerFactory.getLogger(WriteRunningTx.class);
+
+    private final String type;
+    private final RemoteDeviceId id;
+
+    public NetconfRpcFutureCallback(final String prefix, final RemoteDeviceId id) {
+        this.type = prefix;
+        this.id = id;
+    }
+
+    @Override
+    public void onSuccess(final RpcResult<CompositeNode> result) {
+        if(result.isSuccessful()) {
+            LOG.trace("{}: " + type + " invoked successfully", id);
+        } else {
+            onUnsuccess(result);
+        }
+    }
+
+    protected void onUnsuccess(final RpcResult<CompositeNode> result) {
+        LOG.warn("{}: " + type + " invoked unsuccessfully: {}", id, result.getErrors());
+    }
+
+    @Override
+    public void onFailure(final Throwable t) {
+        LOG.warn("{}: " + type + " failed.", id, t);
+    }
+}
index 0607e4b..ce97541 100644 (file)
@@ -2,21 +2,27 @@ package org.opendaylight.controller.sal.connect.netconf.sal.tx;
 
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.inOrder;
 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME;
 
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Collections;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
+import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities;
+import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.controller.sal.core.api.RpcImplementation;
@@ -40,8 +46,11 @@ public class NetconfDeviceWriteOnlyTxTest {
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        doReturn(Futures.<RpcResult<CompositeNode>>immediateFailedFuture(new IllegalStateException("Failed tx")))
-        .doReturn(Futures.immediateFuture(RpcResultBuilder.<CompositeNode>success().build()))
+        ListenableFuture<RpcResult<CompositeNode>> successFuture = Futures.immediateFuture(RpcResultBuilder.<CompositeNode>success().build());
+
+        doReturn(successFuture)
+                .doReturn(Futures.<RpcResult<CompositeNode>>immediateFailedFuture(new IllegalStateException("Failed tx")))
+                .doReturn(successFuture)
                 .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class));
 
         yangIId = YangInstanceIdentifier.builder().node(QName.create("namespace", "2012-12-12", "name")).build();
@@ -49,31 +58,45 @@ public class NetconfDeviceWriteOnlyTxTest {
     }
 
     @Test
-    public void testDiscardCahnges() {
-        final NetconfDeviceWriteOnlyTx tx = new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, true, true);
+    public void testDiscardChanges() {
+        final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc), normalizer,
+                NetconfSessionCapabilities.fromStrings(Collections.<String>emptySet()));
         final CheckedFuture<Void, TransactionCommitFailedException> submitFuture = tx.submit();
         try {
             submitFuture.checkedGet();
         } catch (final TransactionCommitFailedException e) {
             // verify discard changes was sent
-            verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
+            final InOrder inOrder = inOrder(rpc);
+            inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_LOCK_QNAME, NetconfBaseOps.getLockContent(NETCONF_CANDIDATE_QNAME));
+            inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
+            inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
+            inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME, NetconfBaseOps.getUnLockContent(NETCONF_CANDIDATE_QNAME));
             return;
         }
 
         fail("Submit should fail");
     }
 
-
     @Test
-    public void testDiscardCahngesNotSentWithoutCandidate() {
+    public void testDiscardChangesNotSentWithoutCandidate() {
         doReturn(Futures.immediateFuture(RpcResultBuilder.<CompositeNode>success().build()))
         .doReturn(Futures.<RpcResult<CompositeNode>>immediateFailedFuture(new IllegalStateException("Failed tx")))
                 .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class));
 
-        final NetconfDeviceWriteOnlyTx tx = new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, false, true);
-        tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
-        verify(rpc).invokeRpc(eq(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), any(CompositeNode.class));
-        verifyNoMoreInteractions(rpc);
+        final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc), normalizer,
+                NetconfSessionCapabilities.fromStrings(Collections.<String>emptySet()));
+        try {
+            tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+        } catch (final Exception e) {
+            // verify discard changes was sent
+            final InOrder inOrder = inOrder(rpc);
+            inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_LOCK_QNAME, NetconfBaseOps.getLockContent(NETCONF_RUNNING_QNAME));
+            inOrder.verify(rpc).invokeRpc(same(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), any(CompositeNode.class));
+            inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME, NetconfBaseOps.getUnLockContent(NETCONF_RUNNING_QNAME));
+            return;
+        }
+
+        fail("Delete should fail");
     }
 
 }
index 05cd598..cda940f 100644 (file)
@@ -101,7 +101,11 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter {
                     if (future.isSuccess()) {
                         handleSshAuthenticated(session, ctx);
                     } else {
-                        handleSshSetupFailure(ctx, future.getException());
+                        // Exception does not have to be set in the future, add simple exception in such case
+                        final Throwable exception = future.getException() == null ?
+                                new IllegalStateException("Authentication failed") :
+                                future.getException();
+                        handleSshSetupFailure(ctx, exception);
                     }
                 }
             });