From 7ae3f4cf8450a7c32bc653fa629a3dc58fdb7047 Mon Sep 17 00:00:00 2001 From: Andrej Mak Date: Fri, 30 Sep 2016 12:49:55 +0200 Subject: [PATCH] Bug 6637 - Make Netconf test-tool simulate buggy behavior 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 --- .../test/tool/NetconfDeviceSimulator.java | 61 ++++--- .../netconf/test/tool/TesttoolParameters.java | 12 ++ .../netconf/test/tool/customrpc/Rpc.java | 29 ++++ .../test/tool/customrpc/RpcMapping.java | 163 ++++++++++++++++++ .../netconf/test/tool/customrpc/Rpcs.java | 28 +++ .../customrpc/SettableOperationProvider.java | 63 +++++++ .../test/tool/customrpc/SettableRpc.java | 68 ++++++++ .../netconf/test/tool/customrpc/XmlData.java | 23 +++ 8 files changed, 425 insertions(+), 22 deletions(-) create mode 100644 netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java create mode 100644 netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java create mode 100644 netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java create mode 100644 netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java create mode 100644 netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java create mode 100644 netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java index 165decc6d4..3ade59f8a5 100644 --- a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java @@ -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 capabilities, final boolean exi, final int generateConfigsTimeout, - final Optional notificationsFile, final boolean mdSal, final Optional initialConfigXMLFile, - final SchemaSourceProvider sourceProvider) { + private NetconfServerDispatcherImpl createDispatcher(final Set capabilities, + final SchemaSourceProvider sourceProvider, + final TesttoolParameters params) { final Set transformedCapabilities = Sets.newHashSet(Collections2.transform(capabilities, new Function() { @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 serverCapabilities = exi + final Set 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 sourceProvider, + final TesttoolParameters params, + final Set 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 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 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() { @Override public CheckedFuture getSource(final SourceIdentifier sourceIdentifier) { return schemaRepo.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class); } - }); + }, params); int currentPort = params.startingPort; diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java index 8218d73a15..f08daee520 100644 --- a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java @@ -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> getThreadsPayloads(final List 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 index 0000000000..c232aa9e36 --- /dev/null +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java @@ -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 output; + + XmlData getInput() { + return input; + } + + List 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 index 0000000000..5d3f7eb14c --- /dev/null +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java @@ -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 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 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 getResponse(final XmlElement input) { + final Collection responses = requestToResponseMap.get(new Request(input)); + final Iterator 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 e1Children = e1.getChildElements(); + final List e2Children = e2.getChildElements(); + if (e1Children.size() != e2Children.size()) { + return false; + } + final Iterator e1Iterator = e1Children.iterator(); + final Iterator 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 e1Attrs = e1.getAttributes(); + final Map e2Attrs = e2.getAttributes(); + final Iterator> e1AttrIt = e1Attrs.entrySet().iterator(); + final Iterator> e2AttrIt = e2Attrs.entrySet().iterator(); + while (e1AttrIt.hasNext() && e2AttrIt.hasNext()) { + final Map.Entry e1Next = e1AttrIt.next(); + final Map.Entry 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 index 0000000000..9a2ad3698a --- /dev/null +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java @@ -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 rpcs; + + List 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 index 0000000000..d811fe70f4 --- /dev/null +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java @@ -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 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 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 index 0000000000..fddc0be003 --- /dev/null +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java @@ -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 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 index 0000000000..69fb66060f --- /dev/null +++ b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java @@ -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(); + } + +} -- 2.36.6