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>
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;
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;
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
}
}
}));
-
- 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;
@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");
.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;
}
}
}
}
+ 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) {
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}