package org.opendaylight.netconf.mdsal.connector.ops;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Optional;
import com.google.common.base.Strings;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Base64;
import org.opendaylight.netconf.api.DocumentedException;
import org.opendaylight.netconf.api.xml.XmlElement;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.api.xml.XmlUtil;
import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
abstract class AbstractConfigOperation extends AbstractSingletonNetconfOperation {
+ static final String URL_KEY = "url";
+ static final String CONFIG_KEY = "config";
+ private static final int TIMEOUT_MS = 5000;
protected AbstractConfigOperation(final String netconfSessionIdForReporting) {
super(netconfSessionIdForReporting);
return elementsByTagName;
}
+
+ static XmlElement getConfigElement(final XmlElement parent) throws DocumentedException {
+ final Optional<XmlElement> configElement = parent.getOnlyChildElementOptionally(CONFIG_KEY);
+ if (configElement.isPresent()) {
+ return configElement.get();
+ } else {
+ final Optional<XmlElement> urlElement = parent.getOnlyChildElementOptionally(URL_KEY);
+ if (!urlElement.isPresent()) {
+ throw new DocumentedException("Invalid RPC, neither <config> not <url> element is present",
+ DocumentedException.ErrorType.PROTOCOL,
+ DocumentedException.ErrorTag.MISSING_ELEMENT,
+ DocumentedException.ErrorSeverity.ERROR);
+ }
+
+ final Document document = getDocumentFromUrl(urlElement.get().getTextContent());
+ return XmlElement.fromDomElementWithExpected(document.getDocumentElement(), CONFIG_KEY,
+ XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
+ }
+ }
+
+ /**
+ * Parses XML Document available at given URL.
+ *
+ * <p>JDK8 supports URL schemes that include http, https, file, and jar, but {@link URLStreamHandler}s for other
+ * protocols (e.g. ftp) may be available.
+ *
+ * @param url URL as defined in RFC 2396
+ * @see URL#URL(String, String, int, String)
+ */
+ private static Document getDocumentFromUrl(final String url) throws DocumentedException {
+ try (InputStream input = openConnection(new URL(url))) {
+ return XmlUtil.readXmlToDocument(input);
+ } catch (MalformedURLException e) {
+ throw new DocumentedException(url + " URL is invalid or unsupported", e,
+ DocumentedException.ErrorType.APPLICATION,
+ DocumentedException.ErrorTag.INVALID_VALUE,
+ DocumentedException.ErrorSeverity.ERROR);
+ } catch (IOException e) {
+ throw new DocumentedException("Could not open URL:" + url, e,
+ DocumentedException.ErrorType.APPLICATION,
+ DocumentedException.ErrorTag.OPERATION_FAILED,
+ DocumentedException.ErrorSeverity.ERROR);
+ } catch (SAXException e) {
+ throw new DocumentedException("Could not parse XML at" + url, e,
+ DocumentedException.ErrorType.APPLICATION,
+ DocumentedException.ErrorTag.OPERATION_FAILED,
+ DocumentedException.ErrorSeverity.ERROR);
+ }
+ }
+
+ private static InputStream openConnection(final URL url) throws IOException {
+ final URLConnection connection = url.openConnection();
+ connection.setConnectTimeout(TIMEOUT_MS);
+ connection.setReadTimeout(TIMEOUT_MS);
+
+ // Support Basic Authentication scheme, e.g. http://admin:admin@localhost:8000/config.conf
+ if (url.getUserInfo() != null) {
+ String basicAuth = "Basic " + Base64.getUrlEncoder().encodeToString(url.getUserInfo().getBytes(UTF_8));
+ connection.setRequestProperty("Authorization", basicAuth);
+ }
+ return connection.getInputStream();
+ }
}
package org.opendaylight.netconf.mdsal.connector.ops;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import java.net.URI;
import java.net.URISyntaxException;
return schemaNode.get();
}
- protected static Datastore extractTargetParameter(final XmlElement operationElement, final String operationName)
+ protected static XmlElement extractTargetElement(final XmlElement operationElement, final String operationName)
throws DocumentedException {
final NodeList elementsByTagName = getElementsByTagName(operationElement, TARGET_KEY);
// Direct lookup instead of using XmlElement class due to performance
throw new DocumentedException("Multiple target elements", ErrorType.RPC, ErrorTag.UNKNOWN_ATTRIBUTE,
ErrorSeverity.ERROR);
} else {
- final XmlElement targetChildNode =
- XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
- return Datastore.valueOf(targetChildNode.getName());
+ return XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
}
}
-
- protected static XmlElement getElement(final XmlElement parent, final String elementName)
- throws DocumentedException {
- final Optional<XmlElement> childNode = parent.getOnlyChildElementOptionally(elementName);
- if (!childNode.isPresent()) {
- throw new DocumentedException(elementName + " element is missing",
- ErrorType.PROTOCOL,
- ErrorTag.MISSING_ELEMENT,
- ErrorSeverity.ERROR);
- }
-
- return childNode.get();
- }
}
package org.opendaylight.netconf.mdsal.connector.ops;
+import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
+
import com.google.common.base.Optional;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.dom.DOMResult;
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.dom.api.DOMDataReadWriteTransaction;
import org.opendaylight.netconf.api.DocumentedException;
import org.opendaylight.netconf.api.DocumentedException.ErrorSeverity;
import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
public final class CopyConfig extends AbstractEdit {
private static final String OPERATION_NAME = "copy-config";
- private static final String CONFIG_KEY = "config";
private static final String SOURCE_KEY = "source";
+ private static final XMLOutputFactory XML_OUTPUT_FACTORY;
+
+ static {
+ XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
+ XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+ }
// Top-level "data" node without child nodes
private static final ContainerNode EMPTY_ROOT_NODE = Builders.containerBuilder()
@Override
protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
- throws DocumentedException {
- final Datastore targetDatastore = extractTargetParameter(operationElement, OPERATION_NAME);
- if (targetDatastore == Datastore.running) {
+ throws DocumentedException {
+ final XmlElement targetElement = extractTargetElement(operationElement, OPERATION_NAME);
+ final String target = targetElement.getName();
+ if (Datastore.running.toString().equals(target)) {
throw new DocumentedException("edit-config on running datastore is not supported",
- ErrorType.PROTOCOL,
- ErrorTag.OPERATION_NOT_SUPPORTED,
- ErrorSeverity.ERROR);
+ ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED,
+ ErrorSeverity.ERROR);
+ } else if (Datastore.candidate.toString().equals(target)) {
+ copyToCandidate(operationElement);
+ } else if (URL_KEY.equals(target)) {
+ copyToUrl(targetElement, operationElement);
+ } else {
+ throw new DocumentedException("Unsupported target: " + target,
+ ErrorType.PROTOCOL,
+ ErrorTag.BAD_ELEMENT,
+ ErrorSeverity.ERROR);
}
- final XmlElement configElement = extractConfigParameter(operationElement);
+ return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
+ }
+
+ private void copyToCandidate(final XmlElement operationElement)
+ throws DocumentedException {
+ final XmlElement source = getSourceElement(operationElement);
+ final List<XmlElement> configElements = getConfigElement(source).getChildElements();
// <copy-config>, unlike <edit-config>, always replaces entire configuration,
// so remove old configuration first:
rwTx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.EMPTY, EMPTY_ROOT_NODE);
// Then create nodes present in the <config> element:
- for (final XmlElement element : configElement.getChildElements()) {
+ for (final XmlElement element : configElements) {
final String ns = element.getNamespace();
final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element);
final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
// Doing merge instead of put to support top-level list:
rwTx.merge(LogicalDatastoreType.CONFIGURATION, path, data);
}
- return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
}
- private static XmlElement extractConfigParameter(final XmlElement operationElement) throws DocumentedException {
- final XmlElement source = getElement(operationElement, SOURCE_KEY);
- return getElement(source, CONFIG_KEY);
+ private static XmlElement getSourceElement(final XmlElement parent) throws DocumentedException {
+ final Optional<XmlElement> sourceElement = parent.getOnlyChildElementOptionally(SOURCE_KEY);
+ if (!sourceElement.isPresent()) {
+ throw new DocumentedException("<source> element is missing",
+ DocumentedException.ErrorType.PROTOCOL,
+ DocumentedException.ErrorTag.MISSING_ELEMENT,
+ DocumentedException.ErrorSeverity.ERROR);
+ }
+
+ return sourceElement.get();
+ }
+
+ private void copyToUrl(final XmlElement urlElement, final XmlElement operationElement) throws DocumentedException {
+ final String url = urlElement.getTextContent();
+ if (!url.startsWith("file:")) {
+ throw new DocumentedException("Unsupported <url> protocol: " + url,
+ ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED,
+ ErrorSeverity.ERROR);
+ }
+
+ // Read data from datastore:
+ final XmlElement source = getSourceElement(operationElement).getOnlyChildElement();
+ final ContainerNode data = readData(source);
+
+ // Transform NN to XML:
+ final Document document = operationElement.getDomElement().getOwnerDocument();
+ final Node node = transformNormalizedNode(document, data);
+
+ // Save XML to file:
+ final String xml = XmlUtil.toString((Element) node);
+ try {
+ final Path file = Paths.get(new URI(url));
+ Files.write(file, xml.getBytes(StandardCharsets.UTF_8));
+ } catch (URISyntaxException | IllegalArgumentException e) {
+ throw new DocumentedException("Invalid URI: " + url, e,
+ ErrorType.RPC,
+ ErrorTag.INVALID_VALUE,
+ ErrorSeverity.ERROR);
+ } catch (IOException e) {
+ throw new DocumentedException("Failed to write : " + url, e,
+ ErrorType.APPLICATION,
+ ErrorTag.OPERATION_FAILED,
+ ErrorSeverity.ERROR);
+ }
+ }
+
+ private ContainerNode readData(final XmlElement source) throws DocumentedException {
+ final Datastore sourceDatastore = getDatastore(source);
+ final DOMDataReadWriteTransaction rwTx = getTransaction(sourceDatastore);
+ final YangInstanceIdentifier dataRoot = YangInstanceIdentifier.EMPTY;
+ try {
+ final Optional<NormalizedNode<?, ?>> normalizedNodeOptional = rwTx.read(
+ LogicalDatastoreType.CONFIGURATION, dataRoot).checkedGet();
+ if (sourceDatastore == Datastore.running) {
+ transactionProvider.abortRunningTransaction(rwTx);
+ }
+ return (ContainerNode) normalizedNodeOptional.get();
+ } catch (ReadFailedException e) {
+ throw new IllegalStateException("Unable to read data " + dataRoot, e);
+ }
+ }
+
+ private static Datastore getDatastore(final XmlElement source) throws DocumentedException {
+ try {
+ return Datastore.valueOf(source.getName());
+ } catch (IllegalArgumentException e) {
+ throw new DocumentedException("Unsupported source for <url> target", e,
+ ErrorType.PROTOCOL,
+ ErrorTag.OPERATION_NOT_SUPPORTED,
+ ErrorSeverity.ERROR);
+ }
+ }
+
+ private DOMDataReadWriteTransaction getTransaction(final Datastore datastore) throws DocumentedException {
+ if (datastore == Datastore.candidate) {
+ return transactionProvider.getOrCreateTransaction();
+ } else if (datastore == Datastore.running) {
+ return transactionProvider.createRunningTransaction();
+ }
+ throw new DocumentedException("Incorrect Datastore: ", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
+ ErrorSeverity.ERROR);
+ }
+
+ private Node transformNormalizedNode(final Document document, final ContainerNode data) {
+ final Element configElement = document.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, CONFIG_KEY);
+ final DOMResult result = new DOMResult(configElement);
+ try {
+ final XMLStreamWriter xmlWriter = XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
+ final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
+ schemaContext.getCurrentContext(), SchemaPath.ROOT);
+
+ final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
+ for (DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> child : data.getValue()) {
+ nnWriter.write(child);
+ }
+ nnWriter.flush();
+ xmlWriter.flush();
+ } catch (XMLStreamException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ return result.getNode();
}
@Override
private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
private static final String OPERATION_NAME = "edit-config";
- private static final String CONFIG_KEY = "config";
private static final String DEFAULT_OPERATION_KEY = "default-operation";
private final TransactionProvider transactionProvider;
@Override
protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
throws DocumentedException {
- final Datastore targetDatastore = extractTargetParameter(operationElement, OPERATION_NAME);
+ final XmlElement targetElement = extractTargetElement(operationElement, OPERATION_NAME);
+ final Datastore targetDatastore = Datastore.valueOf(targetElement.getName());
if (targetDatastore == Datastore.running) {
throw new DocumentedException("edit-config on running datastore is not supported",
ErrorType.PROTOCOL,
final ModifyAction defaultAction = getDefaultOperation(operationElement);
- final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
+ final XmlElement configElement = getConfigElement(operationElement);
for (final XmlElement element : configElement.getChildElements()) {
final String ns = element.getNamespace();
return executeOperation(editConfig, resource);
}
+ protected Document edit(final Document request) throws Exception {
+ final EditConfig editConfig = new EditConfig(SESSION_ID_FOR_REPORTING, currentSchemaContext,
+ transactionProvider);
+ return executeOperation(editConfig, request);
+ }
+
protected Document get() throws Exception {
final Get get = new Get(SESSION_ID_FOR_REPORTING, currentSchemaContext, transactionProvider);
return executeOperation(get, "messages/mapping/get.xml");
protected static Document executeOperation(final NetconfOperation op, final String filename) throws Exception {
final Document request = XmlFileLoader.xmlFileToDocument(filename);
- final Document response = op.handle(request, NetconfOperationChainedExecution.EXECUTION_TERMINATION_POINT);
+ return executeOperation(op, request);
+ }
+ protected static Document executeOperation(final NetconfOperation op, final Document request) throws Exception {
+ final Document response = op.handle(request, NetconfOperationChainedExecution.EXECUTION_TERMINATION_POINT);
LOG.debug("Got response {}", response);
return response;
}
import static org.junit.Assert.fail;
import static org.opendaylight.yangtools.yang.test.util.YangParserTestUtils.parseYangResources;
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.opendaylight.netconf.api.DocumentedException;
import org.opendaylight.netconf.api.DocumentedException.ErrorSeverity;
import org.opendaylight.netconf.api.DocumentedException.ErrorTag;
import org.opendaylight.netconf.api.DocumentedException.ErrorType;
+import org.opendaylight.netconf.api.xml.XmlUtil;
import org.opendaylight.netconf.util.test.XmlFileLoader;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
public class CopyConfigTest extends AbstractNetconfOperationTest {
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
@Override
protected SchemaContext getSchemaContext() {
public void testConfigMissing() throws Exception {
try {
copyConfig("messages/mapping/copyConfigs/copyConfig_no_config.xml");
- fail("Should have failed - <config> element is missing");
+ fail("Should have failed - neither <config> nor <url> element is present");
} catch (final DocumentedException e) {
assertTrue(e.getErrorSeverity() == ErrorSeverity.ERROR);
assertTrue(e.getErrorTag() == ErrorTag.MISSING_ELEMENT);
"messages/mapping/copyConfigs/copyConfig_choices_control.xml"));
}
+ @Test
+ public void testConfigFromFile() throws Exception {
+ // Ask class loader for URI of config file and use it as <url> in <copy-config> RPC:
+ final String template = XmlFileLoader.fileToString("messages/mapping/copyConfigs/copyConfig_from_file.xml");
+ final URI uri = getClass().getClassLoader()
+ .getResource("messages/mapping/copyConfigs/config_file_valid.xml").toURI();
+ final String copyConfig = template.replaceFirst("URL", uri.toString());
+ final Document request = XmlUtil.readXmlToDocument(copyConfig);
+
+ verifyResponse(copyConfig(request), RPC_REPLY_OK);
+ verifyResponse(getConfigCandidate(), XmlFileLoader.xmlFileToDocument(
+ "messages/mapping/copyConfigs/copyConfig_from_file_control.xml"));
+ }
+
+ @Test
+ public void testConfigFromInvalidUrl() throws Exception {
+ try {
+ copyConfig("messages/mapping/copyConfigs/copyConfig_invalid_url.xml");
+ fail("Should have failed - provided <url> is not valid");
+ } catch (final DocumentedException e) {
+ assertTrue(e.getErrorSeverity() == ErrorSeverity.ERROR);
+ assertTrue(e.getErrorTag() == ErrorTag.INVALID_VALUE);
+ assertTrue(e.getErrorType() == ErrorType.APPLICATION);
+ assertTrue(e.getCause() instanceof MalformedURLException);
+ }
+ }
+
+ @Test
+ public void testExternalConfigInvalid() throws Exception {
+ try {
+ // Ask class loader for URI of config file and use it as <url> in <copy-config> RPC:
+ final String template = XmlFileLoader.fileToString("messages/mapping/copyConfigs/copyConfig_from_file.xml");
+ final URI uri = getClass().getClassLoader()
+ .getResource("messages/mapping/copyConfigs/config_file_invalid.xml").toURI();
+ final String copyConfig = template.replaceFirst("URL", uri.toString());
+ final Document request = XmlUtil.readXmlToDocument(copyConfig);
+ copyConfig(request);
+ fail("Should have failed - provided config is not valid XML");
+ } catch (final DocumentedException e) {
+ assertTrue(e.getErrorSeverity() == ErrorSeverity.ERROR);
+ assertTrue(e.getErrorTag() == ErrorTag.OPERATION_FAILED);
+ assertTrue(e.getErrorType() == ErrorType.APPLICATION);
+ assertTrue(e.getCause() instanceof SAXException);
+ }
+ }
+
+ @Test
+ public void testCopyToFile() throws Exception {
+ // Initialize config:
+ verifyResponse(copyConfig("messages/mapping/copyConfigs/copyConfig_top_modules.xml"), RPC_REPLY_OK);
+ verifyResponse(getConfigCandidate(), XmlFileLoader.xmlFileToDocument(
+ "messages/mapping/copyConfigs/copyConfig_top_modules_control.xml"));
+
+ // Load copy-config template and replace URL with the URI of target file:
+ final String template = XmlFileLoader.fileToString("messages/mapping/copyConfigs/copyConfig_to_file.xml");
+ final File outFile = new File(tmpDir.getRoot(),"test-copy-to-file.xml");
+ final String copyConfig = template.replaceFirst("URL", outFile.toURI().toString());
+ final Document request = XmlUtil.readXmlToDocument(copyConfig);
+
+ // Invoke copy-config RPC:
+ verifyResponse(copyConfig(request), RPC_REPLY_OK);
+
+ // Check if outFile was created with expected content:
+ verifyResponse(XmlUtil.readXmlToDocument(new FileInputStream(outFile)),
+ XmlFileLoader.xmlFileToDocument("messages/mapping/copyConfigs/copyConfig_to_file_control.xml"));
+ }
+
+ @Test
+ public void testUnsupportedTargetUrlProtocol() throws Exception {
+ try {
+ copyConfig("messages/mapping/copyConfigs/copyConfig_to_unsupported_url_protocol.xml");
+ fail("Should have failed - exporting config to http server is not supported");
+ } catch (final DocumentedException e) {
+ assertTrue(e.getErrorSeverity() == ErrorSeverity.ERROR);
+ assertTrue(e.getErrorTag() == ErrorTag.OPERATION_NOT_SUPPORTED);
+ assertTrue(e.getErrorType() == ErrorType.PROTOCOL);
+ }
+ }
+
+ @Test
+ public void testCopyToFileFromRunning() throws Exception {
+ // Load copy-config template and replace URL with the URI of target file:
+ final String template =
+ XmlFileLoader.fileToString("messages/mapping/copyConfigs/copyConfig_to_file_from_running.xml");
+ final File outFile = new File(tmpDir.getRoot(),"test-copy-to-file-from-running.xml");
+ final String copyConfig = template.replaceFirst("URL", outFile.toURI().toString());
+ final Document request = XmlUtil.readXmlToDocument(copyConfig);
+
+ // Invoke copy-config RPC:
+ verifyResponse(copyConfig(request), RPC_REPLY_OK);
+
+ // Check if outFile was created with expected content:
+ verifyResponse(XmlUtil.readXmlToDocument(new FileInputStream(outFile)),
+ XmlFileLoader.xmlFileToDocument(
+ "messages/mapping/copyConfigs/copyConfig_to_file_from_running_control.xml"));
+
+ }
+
+ @Test
+ public void testRemoteToRemoteOperationIsNotSupported() throws Exception {
+ try {
+ copyConfig("messages/mapping/copyConfigs/copyConfig_url_remote_to_remote.xml");
+ fail("Should have failed - remote to remote operations are not supported");
+ } catch (final DocumentedException e) {
+ assertTrue(e.getErrorSeverity() == ErrorSeverity.ERROR);
+ assertTrue(e.getErrorTag() == ErrorTag.OPERATION_NOT_SUPPORTED);
+ assertTrue(e.getErrorType() == ErrorType.PROTOCOL);
+ }
+ }
+
private Document copyConfig(final String resource) throws Exception {
final CopyConfig copyConfig = new CopyConfig(SESSION_ID_FOR_REPORTING, getCurrentSchemaContext(),
getTransactionProvider());
return executeOperation(copyConfig, resource);
}
+
+ private Document copyConfig(final Document request) throws Exception {
+ final CopyConfig copyConfig = new CopyConfig(SESSION_ID_FOR_REPORTING, getCurrentSchemaContext(),
+ getTransactionProvider());
+ return executeOperation(copyConfig, request);
+ }
}
import static org.junit.Assert.fail;
import java.io.StringWriter;
+import java.net.URI;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
}
}
+ @Test
+ public void testConfigMissing() throws Exception {
+ try {
+ edit("messages/mapping/editConfigs/editConfig_no_config.xml");
+ fail("Should have failed - neither <config> nor <url> element is present");
+ } catch (final DocumentedException e) {
+ assertTrue(e.getErrorSeverity() == ErrorSeverity.ERROR);
+ assertTrue(e.getErrorTag() == ErrorTag.MISSING_ELEMENT);
+ assertTrue(e.getErrorType() == ErrorType.PROTOCOL);
+ }
+ }
+
@Test
public void testEditRunning() throws Exception {
try {
verifyResponse(commit(), RPC_REPLY_OK);
assertEmptyDatastore(getConfigRunning());
}
+
+ @Test
+ public void testEditUsingConfigFromFile() throws Exception {
+ // Ask class loader for URI of config file and use it as <url> in <edit-config> RPC:
+ final String template = XmlFileLoader.fileToString("messages/mapping/editConfigs/editConfig_from_file.xml");
+ final URI uri = getClass().getClassLoader().getResource("messages/mapping/editConfigs/config_file.xml").toURI();
+ final String copyConfig = template.replaceFirst("URL", uri.toString());
+ final Document request = XmlUtil.readXmlToDocument(copyConfig);
+
+ verifyResponse(edit(request), RPC_REPLY_OK);
+ verifyResponse(getConfigCandidate(), XmlFileLoader.xmlFileToDocument(
+ "messages/mapping/editConfigs/editConfig_from_file_control.xml"));
+ }
}
--- /dev/null
+This is not a valid XML
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="urn:opendaylight:mdsal:mapping:test">
+ <modules>
+ <module>
+ <id>module1</id>
+ </module>
+ <module>
+ <id>module2</id>
+ </module>
+ </modules>
+ </top>
+</config>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <copy-config>
+ <target>
+ <candidate/>
+ </target>
+ <source>
+ <url>URL</url>
+ </source>
+ </copy-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc-reply a="64" id="a" message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlnx="a:b:c:d">
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="urn:opendaylight:mdsal:mapping:test">
+ <modules>
+ <module>
+ <id>module1</id>
+ </module>
+ <module>
+ <id>module2</id>
+ </module>
+ </modules>
+ </top>
+ </data>
+</rpc-reply>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <copy-config>
+ <target>
+ <candidate/>
+ </target>
+ <source>
+ <url>this is not a valid url</url>
+ </source>
+ </copy-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <copy-config>
+ <source>
+ <candidate/>
+ </source>
+ <target>
+ <url>URL</url>
+ </target>
+ </copy-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="urn:opendaylight:mdsal:mapping:test">
+ <modules>
+ <module>
+ <id>module1</id>
+ </module>
+ <module>
+ <id>module2</id>
+ </module>
+ </modules>
+ </top>
+</config>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <copy-config>
+ <source>
+ <running/>
+ </source>
+ <target>
+ <url>URL</url>
+ </target>
+ </copy-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+</config>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <copy-config>
+ <source>
+ <candidate/>
+ </source>
+ <target>
+ <url>http://foo.bar</url>
+ </target>
+ </copy-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <copy-config>
+ <target>
+ <url>file:foo</url>
+ </target>
+ <source>
+ <url>file:bar</url>
+ </source>
+ </copy-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="urn:opendaylight:mdsal:mapping:test">
+ <modules>
+ <module>
+ <id>module1</id>
+ </module>
+ <module>
+ <id>module2</id>
+ </module>
+ </modules>
+ </top>
+</config>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <edit-config>
+ <target>
+ <candidate/>
+ </target>
+ <url>URL</url>
+ </edit-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc-reply a="64" id="a" message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlnx="a:b:c:d">
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <top xmlns="urn:opendaylight:mdsal:mapping:test">
+ <modules>
+ <module>
+ <id>module1</id>
+ </module>
+ <module>
+ <id>module2</id>
+ </module>
+ </modules>
+ </top>
+ </data>
+</rpc-reply>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2018 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
+ -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <edit-config>
+ <target>
+ <candidate/>
+ </target>
+ </edit-config>
+</rpc>
\ No newline at end of file
public static final String URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0 =
"urn:ietf:params:netconf:capability:exi:1.0";
+ public static final String URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0 =
+ "urn:ietf:params:netconf:capability:candidate:1.0";
+ public static final String URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0 =
+ "urn:ietf:params:netconf:capability:url:1.0?scheme=file";
public static final String URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING =
"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
}
*/
package org.opendaylight.netconf.impl.osgi;
+import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0;
+import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0;
+
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
private static final Schema.Location NETCONF_LOCATION = new Schema.Location(Schema.Location.Enumeration.NETCONF);
private static final List<Schema.Location> NETCONF_LOCATIONS = ImmutableList.of(NETCONF_LOCATION);
private static final BasicCapability CANDIDATE_CAPABILITY =
- new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0");
+ new BasicCapability(URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0);
+ private static final BasicCapability URL_CAPABILITY =
+ new BasicCapability(URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0);
private static final Function<Capability, Uri> CAPABILITY_TO_URI = input -> new Uri(input.getCapabilityUri());
private final NetconfOperationServiceFactory netconfOperationProvider;
private static Set<Capability> setupCapabilities(final Set<Capability> caps) {
Set<Capability> capabilities = new HashSet<>(caps);
capabilities.add(CANDIDATE_CAPABILITY);
+ capabilities.add(URL_CAPABILITY);
// TODO rollback on error not supported EditConfigXmlParser:100
// [RFC6241] 8.5. Rollback-on-Error Capability
// capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:rollback-on-error:1.0"));
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0;
+import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0;
import com.google.common.base.Optional;
import java.net.URI;
@Test
public void testGetCapabilities() throws Exception {
- Capabilities actual = monitoringService.getCapabilities();
List<Uri> exp = new ArrayList<>();
for (Capability capability : capabilities) {
exp.add(new Uri(capability.getCapabilityUri()));
}
- //candidate is added by monitoring service automatically
- exp.add(0, new Uri("urn:ietf:params:netconf:capability:candidate:1.0"));
+ //candidate and url capabilities are added by monitoring service automatically
+ exp.add(new Uri(URN_IETF_PARAMS_NETCONF_CAPABILITY_CANDIDATE_1_0));
+ exp.add(new Uri(URN_IETF_PARAMS_NETCONF_CAPABILITY_URL_1_0));
Capabilities expected = new CapabilitiesBuilder().setCapability(exp).build();
+ Capabilities actual = monitoringService.getCapabilities();
Assert.assertEquals(new HashSet<>(expected.getCapability()), new HashSet<>(actual.getCapability()));
}