Bug 6637 - Make Netconf test-tool simulate buggy behavior 46/46446/10
authorAndrej Mak <andrej.mak@pantheon.tech>
Fri, 30 Sep 2016 10:49:55 +0000 (12:49 +0200)
committerJakub Morvay <jmorvay@cisco.com>
Mon, 7 Nov 2016 08:23:02 +0000 (08:23 +0000)
Define new NetconfOperationServiceFactory which is used by testtool.
This implementation intercepts RPCs and checks if their behavior
is overriden by config. If it is, response defined in config file is
returned. If it is not overriden, handling is delegated to the normal
operation implementation.

Change-Id: I215b8bf52f8229ad54064226abdae0b7fea8f2ff
Signed-off-by: Andrej Mak <andrej.mak@pantheon.tech>
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java [new file with mode: 0644]

index 165decc6d4d6a8a41a05722026753ee375a696ea..3ade59f8a5822b4aa209ec2cc8c0ea8d1f5192c3 100644 (file)
@@ -23,7 +23,6 @@ import io.netty.channel.local.LocalAddress;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.util.HashedWheelTimer;
 import java.io.Closeable;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.BindException;
@@ -56,6 +55,7 @@ import org.opendaylight.netconf.monitoring.osgi.NetconfMonitoringOperationServic
 import org.opendaylight.netconf.ssh.SshProxyServer;
 import org.opendaylight.netconf.ssh.SshProxyServerConfiguration;
 import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
+import org.opendaylight.netconf.test.tool.customrpc.SettableOperationProvider;
 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -102,9 +102,9 @@ public class NetconfDeviceSimulator implements Closeable {
         this.nioExecutor = nioExecutor;
     }
 
-    private NetconfServerDispatcherImpl createDispatcher(final Set<Capability> capabilities, final boolean exi, final int generateConfigsTimeout,
-                                                         final Optional<File> notificationsFile, final boolean mdSal, final Optional<File> initialConfigXMLFile,
-                                                         final SchemaSourceProvider<YangTextSchemaSource> sourceProvider) {
+    private NetconfServerDispatcherImpl createDispatcher(final Set<Capability> capabilities,
+                                                         final SchemaSourceProvider<YangTextSchemaSource> sourceProvider,
+                                                         final TesttoolParameters params) {
 
         final Set<Capability> transformedCapabilities = Sets.newHashSet(Collections2.transform(capabilities, new Function<Capability, Capability>() {
             @Override
@@ -117,49 +117,66 @@ public class NetconfDeviceSimulator implements Closeable {
                 }
             }
         }));
-
-        final SessionIdProvider idProvider = new SessionIdProvider();
-
-        final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory();
-        final NetconfOperationServiceFactory operationProvider = mdSal ? new MdsalOperationProvider(idProvider, transformedCapabilities, schemaContext, sourceProvider) :
-                new SimulatedOperationProvider(idProvider, transformedCapabilities, notificationsFile, initialConfigXMLFile);
-
         transformedCapabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0"));
-
         final NetconfMonitoringService monitoringService1 = new DummyMonitoringService(transformedCapabilities);
+        final SessionIdProvider idProvider = new SessionIdProvider();
 
-        final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService =
-                new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(
-                        new NetconfMonitoringOperationService(monitoringService1));
-        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(operationProvider);
-        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService);
+        final NetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = createOperationServiceFactory(sourceProvider, params, transformedCapabilities, monitoringService1, idProvider);
 
-        final Set<String> serverCapabilities = exi
+        final Set<String> serverCapabilities = params.exi
                 ? NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES
                 : Sets.newHashSet(XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1);
 
         final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new TesttoolNegotiationFactory(
-                hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, generateConfigsTimeout, monitoringService1, serverCapabilities);
+                hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, params.generateConfigsTimeout, monitoringService1, serverCapabilities);
 
         final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(
                 serverNegotiatorFactory);
         return new NetconfServerDispatcherImpl(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup);
     }
 
+    private NetconfOperationServiceFactory createOperationServiceFactory(final SchemaSourceProvider<YangTextSchemaSource> sourceProvider,
+                                                                         final TesttoolParameters params,
+                                                                         final Set<Capability> transformedCapabilities,
+                                                                         final NetconfMonitoringService monitoringService1,
+                                                                         final SessionIdProvider idProvider) {
+        final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory();
+
+        final NetconfOperationServiceFactory operationProvider;
+        if (params.mdSal) {
+            operationProvider = new MdsalOperationProvider(idProvider, transformedCapabilities, schemaContext, sourceProvider);
+        } else {
+            operationProvider = new SimulatedOperationProvider(idProvider, transformedCapabilities,
+                    Optional.fromNullable(params.notificationFile),
+                    Optional.fromNullable(params.initialConfigXMLFile));
+        }
+
+
+        final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService =
+                new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(
+                        new NetconfMonitoringOperationService(monitoringService1));
+        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(operationProvider);
+        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService);
+        if (params.rpcConfig != null) {
+            final SettableOperationProvider settableService = new SettableOperationProvider(params.rpcConfig);
+            aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(settableService);
+        }
+        return aggregatedNetconfOperationServiceFactory;
+    }
+
     public List<Integer> start(final TesttoolParameters params) {
         LOG.info("Starting {}, {} simulated devices starting on port {}", params.deviceCount, params.ssh ? "SSH" : "TCP", params.startingPort);
 
         final SharedSchemaRepository schemaRepo = new SharedSchemaRepository("netconf-simulator");
         final Set<Capability> capabilities = parseSchemasToModuleCapabilities(params, schemaRepo);
 
-        final NetconfServerDispatcherImpl dispatcher = createDispatcher(capabilities, params.exi, params.generateConfigsTimeout,
-                Optional.fromNullable(params.notificationFile), params.mdSal, Optional.fromNullable(params.initialConfigXMLFile),
+        final NetconfServerDispatcherImpl dispatcher = createDispatcher(capabilities,
                 new SchemaSourceProvider<YangTextSchemaSource>() {
                     @Override
                     public CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
                         return schemaRepo.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class);
                     }
-                });
+                }, params);
 
         int currentPort = params.startingPort;
 
index 8218d73a15acc669599b0d03790b978ee9060076..f08daee52008fcd3e8ba6e0d8113cbffd8f4fef1 100644 (file)
@@ -94,6 +94,8 @@ public class TesttoolParameters {
 
     @Arg(dest = "thread-pool-size")
     public int threadPoolSize;
+    @Arg(dest = "rpc-config")
+    public File rpcConfig;
 
     static ArgumentParser getParser() {
         final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
@@ -234,6 +236,11 @@ public class TesttoolParameters {
                 .setDefault(8)
                 .help("The number of threads to keep in the pool, when creating a device simulator. Even if they are idle.")
                 .dest("thread-pool-size");
+        parser.addArgument("--rpc-config")
+                .type(File.class)
+                .help("Rpc config file. It can be used to define custom rpc behavior, or override the default one." +
+                        "Usable for testing buggy device behavior.")
+                .dest("rpc-config");
 
         return parser;
     }
@@ -323,6 +330,11 @@ public class TesttoolParameters {
                 }
             }
         }
+        if (rpcConfig != null) {
+            checkArgument(rpcConfig.exists(), "Rpc config file has to exist");
+            checkArgument(!rpcConfig.isDirectory(), "Rpc config file can't be a directory");
+            checkArgument(rpcConfig.canRead(), "Rpc config file to be readable");
+        }
     }
 
     public ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(final List<Integer> openDevices) {
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java
new file mode 100644 (file)
index 0000000..c232aa9
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+
+class Rpc {
+
+    @XmlElement(name = "input")
+    private XmlData input;
+
+    @XmlElement(name = "output")
+    private List<XmlData> output;
+
+    XmlData getInput() {
+        return input;
+    }
+
+    List<XmlData> getOutput() {
+        return output;
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java
new file mode 100644 (file)
index 0000000..5d3f7eb
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+import javax.xml.bind.JAXB;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+
+/**
+ * Mapping between RPCs and responses.
+ */
+class RpcMapping {
+
+    private final Multimap<Request, Document> requestToResponseMap = ArrayListMultimap.create();
+
+    /**
+     * Creates new mapping from file.
+     *
+     * @param config config file
+     */
+    RpcMapping(final File config) {
+        final Rpcs rpcs = JAXB.unmarshal(config, Rpcs.class);
+        for (final Rpc rpc : rpcs.getRpcs()) {
+            final Stream<Document> stream = rpc.getOutput().stream()
+                    .map(XmlData::getData);
+            final XmlElement element = XmlElement.fromDomDocument(rpc.getInput().getData());
+            requestToResponseMap.putAll(new Request(element), stream::iterator);
+        }
+    }
+
+    /**
+     * Returns response matching given input. If multiple responses are configured for the same
+     * request, every invocation of this method returns next response as they are defined in the config file.
+     * Last configured response is used, when number of invocations is higher than number of configured responses
+     * for rpc.
+     *
+     * @param input request
+     * @return response document, or absent if mapping is not defined
+     */
+    Optional<Document> getResponse(final XmlElement input) {
+        final Collection<Document> responses = requestToResponseMap.get(new Request(input));
+        final Iterator<Document> iterator = responses.iterator();
+        if (iterator.hasNext()) {
+            final Document response = iterator.next();
+            if (iterator.hasNext()) {
+                iterator.remove();
+            }
+            return Optional.of(response);
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Rpc input wrapper. Needed because of custom {@link Request#equals(Object)}
+     * and {@link Request#hashCode()} implementations.
+     */
+    private static class Request {
+        private final XmlElement xmlElement;
+        private final int hashCode;
+
+        private Request(final XmlElement element) {
+            this.xmlElement = element;
+            hashCode = XmlUtil.toString(element)
+                    .replaceAll("message-id=.*(>| )", "") //message id is variable, remove it
+                    .replaceAll("\\s+", "") //remove whitespaces
+                    .hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final Request request = (Request) o;
+            return documentEquals(this.xmlElement, request.xmlElement);
+        }
+
+        @Override
+        public int hashCode() {
+            return hashCode;
+        }
+
+        private static boolean documentEquals(final XmlElement e1, final XmlElement e2) {
+            if (!e1.getNamespaceOptionally().equals(e2.getNamespaceOptionally())) {
+                return false;
+            }
+            if (!e1.getName().equals(e2.getName())) {
+                return false;
+            }
+            if (attributesNotEquals(e1, e2)) {
+                return false;
+            }
+
+            final List<XmlElement> e1Children = e1.getChildElements();
+            final List<XmlElement> e2Children = e2.getChildElements();
+            if (e1Children.size() != e2Children.size()) {
+                return false;
+            }
+            final Iterator<XmlElement> e1Iterator = e1Children.iterator();
+            final Iterator<XmlElement> e2Iterator = e2Children.iterator();
+            while (e1Iterator.hasNext() && e2Iterator.hasNext()) {
+                if (!documentEquals(e1Iterator.next(), e2Iterator.next())) {
+                    return false;
+                }
+            }
+
+            if (e1Children.isEmpty() && e1Children.isEmpty()) {
+                try {
+                    return e1.getTextContent().equals(e2.getTextContent());
+                } catch (final DocumentedException e) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private static boolean attributesNotEquals(final XmlElement e1, final XmlElement e2) {
+            final Map<String, Attr> e1Attrs = e1.getAttributes();
+            final Map<String, Attr> e2Attrs = e2.getAttributes();
+            final Iterator<Map.Entry<String, Attr>> e1AttrIt = e1Attrs.entrySet().iterator();
+            final Iterator<Map.Entry<String, Attr>> e2AttrIt = e2Attrs.entrySet().iterator();
+            while (e1AttrIt.hasNext() && e2AttrIt.hasNext()) {
+                final Map.Entry<String, Attr> e1Next = e1AttrIt.next();
+                final Map.Entry<String, Attr> e2Next = e2AttrIt.next();
+                if (e1Next.getKey().equals("message-id") && e2Next.getKey().equals("message-id")) {
+                    continue;
+                }
+                if (e1Next.getKey().equals("xmlns") && e2Next.getKey().equals("xmlns")) {
+                    continue;
+                }
+                if (!e1Next.getKey().equals(e2Next.getKey())) {
+                    return true;
+                }
+                if (!e1Next.getValue().getValue().equals(e2Next.getValue().getValue())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java
new file mode 100644 (file)
index 0000000..9a2ad36
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "rpcs")
+@XmlAccessorType(XmlAccessType.FIELD)
+class Rpcs {
+
+    @XmlElement(name = "rpc")
+    private List<Rpc> rpcs;
+
+    List<Rpc> getRpcs() {
+        return rpcs;
+    }
+
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java
new file mode 100644 (file)
index 0000000..d811fe7
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+import org.opendaylight.controller.config.util.capability.Capability;
+import org.opendaylight.netconf.api.monitoring.CapabilityListener;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+
+public class SettableOperationProvider implements NetconfOperationServiceFactory {
+
+    private final File rpcConfig;
+
+    public SettableOperationProvider(final File rpcConfig) {
+        this.rpcConfig = rpcConfig;
+    }
+
+    @Override
+    public Set<Capability> getCapabilities() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public AutoCloseable registerCapabilityListener(final CapabilityListener listener) {
+        return () -> {
+            //no op
+        };
+    }
+
+    @Override
+    public NetconfOperationService createService(final String netconfSessionIdForReporting) {
+        return new SettableOperationService(rpcConfig);
+    }
+
+    private static class SettableOperationService implements NetconfOperationService {
+
+        private final SettableRpc rpc;
+
+        private SettableOperationService(final File rpcConfig) {
+            this.rpc = new SettableRpc(rpcConfig);
+        }
+
+        @Override
+        public Set<NetconfOperation> getNetconfOperations() {
+            return Collections.singleton(rpc);
+        }
+
+        @Override
+        public void close() {
+            // no op
+        }
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java
new file mode 100644 (file)
index 0000000..fddc0be
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.io.File;
+import java.util.Optional;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.mapping.api.HandlingPriority;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationChainedExecution;
+import org.w3c.dom.Document;
+
+/**
+ * {@link NetconfOperation} implementation. It can be configured to intercept rpcs with defined input
+ * and reply with defined output. If input isn't defined, rpc handling is delegated to the subsequent
+ * {@link NetconfOperation} which is able to handle it.
+ */
+class SettableRpc implements NetconfOperation {
+
+    private final RpcMapping mapping;
+
+    SettableRpc(final File rpcConfig) {
+        mapping = new RpcMapping(rpcConfig);
+    }
+
+    @Override
+    public HandlingPriority canHandle(final Document message) throws DocumentedException {
+        return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY.increasePriority(1000);
+    }
+
+    @Override
+    public Document handle(final Document requestMessage, final NetconfOperationChainedExecution subsequentOperation)
+            throws DocumentedException {
+        final XmlElement requestElement = XmlElement.fromDomDocument(requestMessage);
+        final XmlElement rpcElement = requestElement.getOnlyChildElement();
+        final String msgId = requestElement.getAttribute(XmlNetconfConstants.MESSAGE_ID);
+        final Optional<Document> response = mapping.getResponse(rpcElement);
+        if (response.isPresent()) {
+            final Document document = response.get();
+            checkForError(document);
+            document.getDocumentElement().setAttribute(XmlNetconfConstants.MESSAGE_ID, msgId);
+            return document;
+        } else if (subsequentOperation.isExecutionTermination()) {
+            throw new DocumentedException("Mapping not found " + XmlUtil.toString(requestMessage),
+                    DocumentedException.ErrorType.APPLICATION, DocumentedException.ErrorTag.OPERATION_NOT_SUPPORTED,
+                    DocumentedException.ErrorSeverity.ERROR);
+        } else {
+            return subsequentOperation.execute(requestMessage);
+        }
+    }
+
+    private void checkForError(final Document document) throws DocumentedException {
+        final XmlElement rpcReply = XmlElement.fromDomDocument(document);
+        if (rpcReply.getOnlyChildElementOptionally("rpc-error").isPresent()) {
+            throw DocumentedException.fromXMLDocument(document);
+        }
+    }
+
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java
new file mode 100644 (file)
index 0000000..69fb660
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import javax.xml.bind.annotation.XmlAnyElement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+class XmlData {
+    @XmlAnyElement
+    private Element data;
+
+    Document getData() {
+        return data.getOwnerDocument();
+    }
+
+}