BUG-732 Add rollback-on-error error-option to edit-config rpcs from netconf-connector 05/6205/3
authorMaros Marsalek <mmarsale@cisco.com>
Tue, 15 Apr 2014 14:00:27 +0000 (16:00 +0200)
committerMaros Marsalek <mmarsale@cisco.com>
Wed, 23 Apr 2014 09:09:43 +0000 (11:09 +0200)
Just in case rollback-on-error capability is supported

+ Add error response warning to netconf-connector

Change-Id: I16251a12b270bd0ed89b39bfccc5251b4b3fe934
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceListener.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTwoPhaseCommitTransaction.java
opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfMapping.java

index 4ea0fa5645b6fe2a772b82a9b0dc72757a7c4edd..8d52950a2954539da471fcbe1f164b01f4eeb1c1 100644 (file)
@@ -119,6 +119,9 @@ public class NetconfDevice implements Provider, //
 
     NetconfDeviceListener listener;
 
 
     NetconfDeviceListener listener;
 
+    private boolean rollbackSupported;
+
+
     public NetconfDevice(String name) {
         this.name = name;
         this.logger = LoggerFactory.getLogger(NetconfDevice.class + "#" + name);
     public NetconfDevice(String name) {
         this.name = name;
         this.logger = LoggerFactory.getLogger(NetconfDevice.class + "#" + name);
@@ -172,7 +175,7 @@ public class NetconfDevice implements Provider, //
         }
     }
 
         }
     }
 
-    void bringUp(final SchemaSourceProvider<String> delegate, final Set<QName> capabilities) {
+    void bringUp(final SchemaSourceProvider<String> delegate, final Set<QName> capabilities, final boolean rollbackSupported) {
         // This has to be called from separate thread, not from netty thread calling onSessionUp in DeviceListener.
         // Reason: delegate.getSchema blocks thread when waiting for response
         // however, if the netty thread is blocked, no incoming message can be processed
         // This has to be called from separate thread, not from netty thread calling onSessionUp in DeviceListener.
         // Reason: delegate.getSchema blocks thread when waiting for response
         // however, if the netty thread is blocked, no incoming message can be processed
@@ -181,6 +184,7 @@ public class NetconfDevice implements Provider, //
         processingExecutor.submit(new Runnable() {
             @Override
             public void run() {
         processingExecutor.submit(new Runnable() {
             @Override
             public void run() {
+                NetconfDevice.this.rollbackSupported = rollbackSupported;
                 remoteSourceProvider = schemaSourceProvider.createInstanceFor(delegate);
                 deviceContextProvider = new NetconfDeviceSchemaContextProvider(NetconfDevice.this, remoteSourceProvider);
                 deviceContextProvider.createContextFromCapabilities(capabilities);
                 remoteSourceProvider = schemaSourceProvider.createInstanceFor(delegate);
                 deviceContextProvider = new NetconfDeviceSchemaContextProvider(NetconfDevice.this, remoteSourceProvider);
                 deviceContextProvider.createContextFromCapabilities(capabilities);
@@ -363,7 +367,7 @@ public class NetconfDevice implements Provider, //
     public DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
             DataModification<InstanceIdentifier, CompositeNode> modification) {
         NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit = new NetconfDeviceTwoPhaseCommitTransaction(this,
     public DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
             DataModification<InstanceIdentifier, CompositeNode> modification) {
         NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit = new NetconfDeviceTwoPhaseCommitTransaction(this,
-                modification, true);
+                modification, true, rollbackSupported);
         try {
             twoPhaseCommit.prepare();
         } catch (InterruptedException e) {
         try {
             twoPhaseCommit.prepare();
         } catch (InterruptedException e) {
index 7ef4569600cb6c10433ec439f16ec6d98e220fa6..d6bf868b75dfaa55059e19f05bdb5bf924f08306 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.controller.sal.connect.netconf;
 
  */
 package org.opendaylight.controller.sal.connect.netconf;
 
+import com.google.common.collect.Sets;
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.FutureListener;
 
 import io.netty.util.concurrent.Future;
 import io.netty.util.concurrent.FutureListener;
 
@@ -39,6 +40,7 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
 class NetconfDeviceListener implements NetconfClientSessionListener {
 import com.google.common.util.concurrent.ListenableFuture;
 
 class NetconfDeviceListener implements NetconfClientSessionListener {
+
     private static final class Request {
         final UncancellableFuture<RpcResult<CompositeNode>> future;
         final NetconfMessage request;
     private static final class Request {
         final UncancellableFuture<RpcResult<CompositeNode>> future;
         final NetconfMessage request;
@@ -80,7 +82,14 @@ class NetconfDeviceListener implements NetconfClientSessionListener {
             delegate = SchemaSourceProviders.noopProvider();
         }
 
             delegate = SchemaSourceProviders.noopProvider();
         }
 
-        device.bringUp(delegate, caps);
+        device.bringUp(delegate, caps, isRollbackSupported(session.getServerCapabilities()));
+
+    }
+
+    private static boolean isRollbackSupported(final Collection<String> serverCapabilities) {
+        // TODO rollback capability cannot be searched for in Set<QName> caps
+        // since this set does not contain module-less capabilities
+        return Sets.newHashSet(serverCapabilities).contains(NetconfMapping.NETCONF_ROLLBACK_ON_ERROR_URI.toString());
     }
 
     private synchronized void tearDown(final Exception e) {
     }
 
     private synchronized void tearDown(final Exception e) {
@@ -137,9 +146,22 @@ class NetconfDeviceListener implements NetconfClientSessionListener {
             requests.poll();
             LOG.debug("Matched {} to {}", r.request, message);
 
             requests.poll();
             LOG.debug("Matched {} to {}", r.request, message);
 
-            // FIXME: this can throw exceptions, which should result
-            // in the future failing
-            NetconfMapping.checkValidReply(r.request, message);
+            try {
+                NetconfMapping.checkValidReply(r.request, message);
+            } catch (IllegalStateException e) {
+                LOG.warn("Invalid request-reply match, reply message contains different message-id", e);
+                r.future.setException(e);
+                return;
+            }
+
+            try {
+                NetconfMapping.checkSuccessReply(message);
+            } catch (IllegalStateException e) {
+                LOG.warn("Error reply from remote device", e);
+                r.future.setException(e);
+                return;
+            }
+
             r.future.set(Rpcs.getRpcResult(true, NetconfMapping.toNotificationNode(message, device.getSchemaContext()),
                     Collections.<RpcError>emptyList()));
         } else {
             r.future.set(Rpcs.getRpcResult(true, NetconfMapping.toNotificationNode(message, device.getSchemaContext()),
                     Collections.<RpcError>emptyList()));
         } else {
index 5f14c264edb4cac6ab3dc1b81ccd8f2b1744a598..8a74b17ac46449c806f745c48e0892a59ec9374b 100644 (file)
@@ -11,9 +11,11 @@ import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NET
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_COMMIT_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_CONFIG_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_EDIT_CONFIG_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_COMMIT_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_CONFIG_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_EDIT_CONFIG_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_ERROR_OPTION_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_OPERATION_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_RUNNING_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_TARGET_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_OPERATION_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_RUNNING_QNAME;
 import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_TARGET_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.ROLLBACK_ON_ERROR_OPTION;
 
 import java.util.Collection;
 import java.util.Collections;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -33,6 +35,7 @@ import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifie
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl;
 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,13 +50,15 @@ class NetconfDeviceTwoPhaseCommitTransaction implements DataCommitTransaction<In
     private final DataModification<InstanceIdentifier, CompositeNode> modification;
     private final NetconfDevice device;
     private final boolean candidateSupported;
     private final DataModification<InstanceIdentifier, CompositeNode> modification;
     private final NetconfDevice device;
     private final boolean candidateSupported;
+    private final boolean rollbackSupported;
 
     public NetconfDeviceTwoPhaseCommitTransaction(NetconfDevice device,
             DataModification<InstanceIdentifier, CompositeNode> modification,
 
     public NetconfDeviceTwoPhaseCommitTransaction(NetconfDevice device,
             DataModification<InstanceIdentifier, CompositeNode> modification,
-            boolean candidateSupported) {
+            boolean candidateSupported, boolean rollbackOnErrorSupported) {
         this.device = Preconditions.checkNotNull(device);
         this.modification = Preconditions.checkNotNull(modification);
         this.candidateSupported = candidateSupported;
         this.device = Preconditions.checkNotNull(device);
         this.modification = Preconditions.checkNotNull(modification);
         this.candidateSupported = candidateSupported;
+        this.rollbackSupported = rollbackOnErrorSupported;
     }
 
     void prepare() throws InterruptedException, ExecutionException {
     }
 
     void prepare() throws InterruptedException, ExecutionException {
@@ -91,7 +96,14 @@ class NetconfDeviceTwoPhaseCommitTransaction implements DataCommitTransaction<In
         } else {
             targetNode = ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
         }
         } else {
             targetNode = ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
         }
+
         Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
         Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
+
+        if(rollbackSupported) {
+            LOG.debug("Rollback-on-error supported, setting {} to {}", NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
+            ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
+        }
+
         ret.add(targetWrapperNode);
         return ret;
     }
         ret.add(targetWrapperNode);
         return ret;
     }
index a8ef4dd2fb262a7ae1e4383faa64d2d24b4e8673..3e729149eb99a485c3026cbd7868304c1a20d057 100644 (file)
@@ -19,6 +19,8 @@ import javax.activation.UnsupportedDataTypeException;
 import javax.annotation.Nullable;
 
 import org.opendaylight.controller.netconf.api.NetconfMessage;
 import javax.annotation.Nullable;
 
 import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
 import org.opendaylight.controller.sal.common.util.Rpcs;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.controller.sal.common.util.Rpcs;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.RpcError;
@@ -52,6 +54,7 @@ public class NetconfMapping {
     public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
     public static String NETCONF_MONITORING_URI = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
     public static URI NETCONF_NOTIFICATION_URI = URI.create("urn:ietf:params:xml:ns:netconf:notification:1.0");
     public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
     public static String NETCONF_MONITORING_URI = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
     public static URI NETCONF_NOTIFICATION_URI = URI.create("urn:ietf:params:xml:ns:netconf:notification:1.0");
+    public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI.create("urn:ietf:params:netconf:capability:rollback-on-error:1.0");
 
     public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf");
     public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc");
 
     public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf");
     public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc");
@@ -71,6 +74,9 @@ public class NetconfMapping {
     public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate");
     public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running");
 
     public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate");
     public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running");
 
+    public static QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option");
+    public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error";
+
     public static QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply");
     public static QName NETCONF_OK_QNAME = QName.create(NETCONF_QNAME, "ok");
     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_OK_QNAME = QName.create(NETCONF_QNAME, "ok");
     public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data");
@@ -248,7 +254,17 @@ public class NetconfMapping {
     public static void checkValidReply(NetconfMessage input, NetconfMessage output) {
         String inputMsgId = input.getDocument().getDocumentElement().getAttribute("message-id");
         String outputMsgId = output.getDocument().getDocumentElement().getAttribute("message-id");
     public static void checkValidReply(NetconfMessage input, NetconfMessage output) {
         String inputMsgId = input.getDocument().getDocumentElement().getAttribute("message-id");
         String outputMsgId = output.getDocument().getDocumentElement().getAttribute("message-id");
-        Preconditions.checkState(inputMsgId.equals(outputMsgId), "Rpc request and reply message IDs must be same.");
+
+        if(inputMsgId.equals(outputMsgId) == false) {
+            String requestXml = XmlUtil.toString(input.getDocument());
+            String responseXml = XmlUtil.toString(output.getDocument());
+            throw new IllegalStateException(String.format("Rpc request and reply message IDs must be same. Request: %s, response: %s", requestXml, responseXml));
+        }
     }
 
     }
 
+    public static void checkSuccessReply(NetconfMessage output) {
+        if(NetconfMessageUtil.isErrorMessage(output)) {
+            throw new IllegalStateException(String.format("Response contains error: %s", XmlUtil.toString(output.getDocument())));
+        }
+    }
 }
 }