import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collections;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
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.DataContainerNode;
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.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.slf4j.Logger;
protected static final String FILTER = "filter";
static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
protected final CurrentSchemaContext schemaContext;
+ private final FilterContentValidator validator;
public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
super(netconfSessionIdForReporting);
this.schemaContext = schemaContext;
+ this.validator = new FilterContentValidator(schemaContext);
}
private static final XMLOutputFactory XML_OUTPUT_FACTORY;
}
}
- private DataSchemaNode getSchemaNodeFromNamespace(final XmlElement element) throws DocumentedException {
-
- try {
- final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(element.getNamespace()), null);
- DataSchemaNode dataSchemaNode = module.getDataChildByName(element.getName());
- if (dataSchemaNode != null) {
- return dataSchemaNode;
- }
- } catch (URISyntaxException e) {
- LOG.debug("Error during parsing of element namespace, this should not happen since namespace of an xml " +
- "element is valid and if the xml was parsed then the URI should be as well");
- throw new IllegalArgumentException("Unable to parse element namespace, this should not happen since " +
- "namespace of an xml element is valid and if the xml was parsed then the URI should be as well");
- }
- throw new DocumentedException("Unable to find node with namespace: " + element.getNamespace() + "in schema context: " + schemaContext.getCurrentContext().toString(),
- ErrorType.application,
- ErrorTag.unknown_namespace,
- ErrorSeverity.error);
- }
-
protected Element serializeNodeWithParentStructure(Document document, YangInstanceIdentifier dataRoot, NormalizedNode node) {
if (!dataRoot.equals(ROOT)) {
return (Element) transformNormalizedNode(document,
}
XmlElement element = filterElement.getOnlyChildElement();
- DataSchemaNode schemaNode = getSchemaNodeFromNamespace(element);
-
- return getReadPointFromNode(YangInstanceIdentifier.builder().build(), filterToNormalizedNode(element, schemaNode));
- }
-
- private YangInstanceIdentifier getReadPointFromNode(final YangInstanceIdentifier pathArg, final NormalizedNode nNode) {
- final YangInstanceIdentifier path = pathArg.node(nNode.getIdentifier());
- if (nNode instanceof DataContainerNode) {
- DataContainerNode node = (DataContainerNode) nNode;
- if (node.getValue().size() == 1) {
- return getReadPointFromNode(path, (NormalizedNode) Lists.newArrayList(node.getValue()).get(0));
- }
- }
- return path;
- }
-
- private NormalizedNode filterToNormalizedNode(XmlElement element, DataSchemaNode schemaNode) throws DocumentedException {
- DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory
- .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext());
-
- final NormalizedNode parsedNode;
-
- if (schemaNode instanceof ContainerSchemaNode) {
- parsedNode = parserFactory.getContainerNodeParser().parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
- } else if (schemaNode instanceof ListSchemaNode) {
- parsedNode = parserFactory.getMapNodeParser().parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
- } else {
- throw new DocumentedException("Schema node of the top level element is not an instance of container or list",
- ErrorType.application, ErrorTag.unknown_element, ErrorSeverity.error);
- }
- return parsedNode;
+ return validator.validate(element);
}
protected static final class GetConfigExecution {
--- /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.mdsal.connector.ops.get;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.MissingNameSpaceException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+/**
+ * Class validates filter content against schema context.
+ */
+public class FilterContentValidator {
+
+ private final CurrentSchemaContext schemaContext;
+
+ /**
+ * @param schemaContext current schema context
+ */
+ public FilterContentValidator(CurrentSchemaContext schemaContext) {
+ this.schemaContext = schemaContext;
+ }
+
+ /**
+ * Validates filter content against this validator schema context. If the filter is valid, method returns {@link YangInstanceIdentifier}
+ * of node which can be used as root for data selection.
+ * @param filterContent filter content
+ * @return YangInstanceIdentifier
+ * @throws DocumentedException if filter content is not valid
+ */
+ public YangInstanceIdentifier validate(XmlElement filterContent) throws DocumentedException {
+ try {
+ final URI namespace = new URI(filterContent.getNamespace());
+ final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(namespace, null);
+ final DataSchemaNode schema = getRootDataSchemaNode(module, namespace, filterContent.getName());
+ final FilterTree filterTree = validateNode(filterContent, schema, new FilterTree(schema.getQName(), Type.OTHER));
+ return getFilterDataRoot(filterTree, YangInstanceIdentifier.builder());
+ } catch (DocumentedException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new DocumentedException("Validation failed. Cause: " + e.getMessage(),
+ DocumentedException.ErrorType.application,
+ DocumentedException.ErrorTag.unknown_namespace,
+ DocumentedException.ErrorSeverity.error);
+ }
+ }
+
+ /**
+ * Returns module's child data node of given name space and name
+ * @param module module
+ * @param nameSpace name space
+ * @param name name
+ * @return child data node schema
+ * @throws DocumentedException if child with given name is not present
+ */
+ private DataSchemaNode getRootDataSchemaNode(Module module, URI nameSpace, String name) throws DocumentedException {
+ final Collection<DataSchemaNode> childNodes = module.getChildNodes();
+ for (DataSchemaNode childNode : childNodes) {
+ final QName qName = childNode.getQName();
+ if (qName.getNamespace().equals(nameSpace) && qName.getLocalName().equals(name)) {
+ return childNode;
+ }
+ }
+ throw new DocumentedException("Unable to find node with namespace: " + nameSpace + "in schema context: " + schemaContext.getCurrentContext().toString(),
+ DocumentedException.ErrorType.application,
+ DocumentedException.ErrorTag.unknown_namespace,
+ DocumentedException.ErrorSeverity.error);
+ }
+
+ /**
+ * Recursively checks filter elements against the schema. Returns tree of nodes QNames as they appear in filter.
+ * @param element element to check
+ * @param parentNodeSchema parent node schema
+ * @param tree parent node tree
+ * @return tree
+ * @throws ValidationException if filter content is not valid
+ */
+ private FilterTree validateNode(XmlElement element, DataSchemaNode parentNodeSchema, FilterTree tree) throws ValidationException {
+ final List<XmlElement> childElements = element.getChildElements();
+ for (XmlElement childElement : childElements) {
+ try {
+ final Deque<DataSchemaNode> path = findSchemaNodeByNameAndNamespace(parentNodeSchema, childElement.getName(), new URI(childElement.getNamespace()));
+ if (path.isEmpty()) {
+ throw new ValidationException(element, childElement);
+ }
+ FilterTree subtree = tree;
+ for (DataSchemaNode dataSchemaNode : path) {
+ subtree = subtree.addChild(dataSchemaNode);
+ }
+ final DataSchemaNode childSchema = path.getLast();
+ validateNode(childElement, childSchema, subtree);
+ } catch (URISyntaxException | MissingNameSpaceException e) {
+ throw new RuntimeException("Wrong namespace in element + " + childElement.toString());
+ }
+ }
+ return tree;
+ }
+
+ /**
+ * Searches for YangInstanceIdentifier of node, which can be used as root for data selection.
+ * It goes as deep in tree as possible. Method stops traversing, when there are multiple child elements
+ * or when it encounters list node.
+ * @param tree QName tree
+ * @param builder builder
+ * @return YangInstanceIdentifier
+ */
+ private YangInstanceIdentifier getFilterDataRoot(FilterTree tree, YangInstanceIdentifier.InstanceIdentifierBuilder builder) {
+ builder.node(tree.getName());
+ while (tree.getChildren().size() == 1) {
+ final FilterTree child = tree.getChildren().iterator().next();
+ if (child.getType() == Type.CHOICE_CASE) {
+ tree = child;
+ continue;
+ }
+ builder.node(child.getName());
+ if (child.getType() == Type.LIST) {
+ return builder.build();
+ }
+ tree = child;
+ }
+ return builder.build();
+ }
+
+ //FIXME this method will also be in yangtools ParserUtils, use that when https://git.opendaylight.org/gerrit/#/c/37031/ will be merged
+ /**
+ * Returns stack of schema nodes via which it was necessary to pass to get schema node with specified
+ * {@code childName} and {@code namespace}
+ *
+ * @param dataSchemaNode
+ * @param childName
+ * @param namespace
+ * @return stack of schema nodes via which it was passed through. If found schema node is direct child then stack
+ * contains only one node. If it is found under choice and case then stack should contains 2*n+1 element
+ * (where n is number of choices through it was passed)
+ */
+ private Deque<DataSchemaNode> findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode,
+ final String childName, final URI namespace) {
+ final Deque<DataSchemaNode> result = new ArrayDeque<>();
+ final List<ChoiceSchemaNode> childChoices = new ArrayList<>();
+ DataSchemaNode potentialChildNode = null;
+ if (dataSchemaNode instanceof DataNodeContainer) {
+ for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
+ if (childNode instanceof ChoiceSchemaNode) {
+ childChoices.add((ChoiceSchemaNode) childNode);
+ } else {
+ final QName childQName = childNode.getQName();
+
+ if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) {
+ if (potentialChildNode == null ||
+ childQName.getRevision().after(potentialChildNode.getQName().getRevision())) {
+ potentialChildNode = childNode;
+ }
+ }
+ }
+ }
+ }
+ if (potentialChildNode != null) {
+ result.push(potentialChildNode);
+ return result;
+ }
+
+ // try to find data schema node in choice (looking for first match)
+ for (final ChoiceSchemaNode choiceNode : childChoices) {
+ for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) {
+ final Deque<DataSchemaNode> resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName,
+ namespace);
+ if (!resultFromRecursion.isEmpty()) {
+ resultFromRecursion.push(concreteCase);
+ resultFromRecursion.push(choiceNode);
+ return resultFromRecursion;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Class represents tree of QNames as they are present in the filter.
+ */
+ private static class FilterTree {
+
+ private final QName name;
+ private final Type type;
+ private final Map<QName, FilterTree> children;
+
+ FilterTree(QName name, Type type) {
+ this.name = name;
+ this.type = type;
+ this.children = new HashMap<>();
+ }
+
+ FilterTree addChild(DataSchemaNode data) {
+ Type type;
+ if (data instanceof ChoiceCaseNode) {
+ type = Type.CHOICE_CASE;
+ } else if (data instanceof ListSchemaNode) {
+ type = Type.LIST;
+ } else {
+ type = Type.OTHER;
+ }
+ final QName name = data.getQName();
+ FilterTree childTree = children.get(name);
+ if (childTree == null) {
+ childTree = new FilterTree(name, type);
+ }
+ children.put(name, childTree);
+ return childTree;
+ }
+
+ Collection<FilterTree> getChildren() {
+ return children.values();
+ }
+
+ QName getName() {
+ return name;
+ }
+
+ Type getType() {
+ return type;
+ }
+ }
+
+ private enum Type {
+ LIST, CHOICE_CASE, OTHER
+ }
+
+ private static class ValidationException extends Exception {
+ public ValidationException(XmlElement parent, XmlElement child) {
+ super("Element " + child + " can't be child of " + parent);
+ }
+ }
+
+}
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
+import com.google.common.io.ByteSource;
import com.google.common.util.concurrent.Futures;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
-
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
-
import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.XMLUnit;
import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
-import com.google.common.io.ByteSource;
-
public class NetconfMDSalMappingTest {
private static final Logger LOG = LoggerFactory.getLogger(NetconfMDSalMappingTest.class);
private static final QName INNER_CHOICE_TEXT = QName.create("urn:opendaylight:mdsal:mapping:test", "2015-02-26", "text");
private static final YangInstanceIdentifier AUGMENTED_CONTAINER_IN_MODULES =
- YangInstanceIdentifier.builder().node(TOP).node(MODULES).build().node(new AugmentationIdentifier(Collections.singleton(AUGMENTED_CONTAINER)));
+ YangInstanceIdentifier.builder().node(TOP).node(MODULES).build();
private static Document RPC_REPLY_OK = null;
verifyResponse(edit("messages/mapping/editConfigs/editConfig-filtering-setup.xml"), RPC_REPLY_OK);
verifyResponse(commit(), RPC_REPLY_OK);
- //TODO uncomment these tests once we can parse KeyedListNode as a selection node, currently you cannot use a KeyedList as a selection node in filter
-// verifyFilterIdentifier("messages/mapping/filters/get-filter-alluser.xml",
-// YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
+ verifyFilterIdentifier("messages/mapping/filters/get-filter-alluser.xml",
+ YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
verifyFilterIdentifier("messages/mapping/filters/get-filter-company-info.xml",
YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
verifyFilterIdentifier("messages/mapping/filters/get-filter-modules-and-admin.xml",
YangInstanceIdentifier.builder().node(TOP).build());
verifyFilterIdentifier("messages/mapping/filters/get-filter-only-names-types.xml",
YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
-// verifyFilterIdentifier("messages/mapping/filters/get-filter-specific-module-type-and-user.xml",
-// YangInstanceIdentifier.builder().node(TOP).build());
-// verifyFilterIdentifier("messages/mapping/filters/get-filter-superuser.xml",
-// YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
+ verifyFilterIdentifier("messages/mapping/filters/get-filter-specific-module-type-and-user.xml",
+ YangInstanceIdentifier.builder().node(TOP).build());
+ verifyFilterIdentifier("messages/mapping/filters/get-filter-superuser.xml",
+ YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
verifyFilterIdentifier("messages/mapping/filters/get-filter-users.xml",
YangInstanceIdentifier.builder().node(TOP).node(USERS).build());
//verifyResponse(edit("messages/mapping/editConfigs/editConfig-filtering-setup2.xml"), RPC_REPLY_OK);
//verifyResponse(commit(), RPC_REPLY_OK);
-// verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-choice.xml",
-// YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).build());
-// verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-case.xml",
-// YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).node(INNER_CHOICE).node(INNER_CHOICE_TEXT).build());
+ verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-choice.xml",
+ YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).build());
+ verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-case.xml",
+ YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).node(INNER_CHOICE).node(INNER_CHOICE_TEXT).build());
// verifyResponse(getConfigWithFilter("messages/mapping/filters/get-filter-augmented-string.xml"),
// XmlFileLoader.xmlFileToDocument("messages/mapping/filters/response-augmented-string.xml"));
TestingGetConfig getConfig = new TestingGetConfig(sessionIdForReporting, currentSchemaContext, transactionProvider);
Document request = XmlFileLoader.xmlFileToDocument(resource);
YangInstanceIdentifier iid = getConfig.getInstanceIdentifierFromDocument(request);
- assertTrue(iid.equals(identifier));
+ assertEquals(identifier, iid);
}
private class TestingGetConfig extends GetConfig{
--- /dev/null
+package org.opendaylight.netconf.mdsal.connector.ops.get;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.InitializationError;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+@RunWith(value = Parameterized.class)
+public class FilterContentValidatorTest {
+
+ private static final int TEST_CASE_COUNT = 8;
+ private final XmlElement filterContent;
+ private final String expected;
+ private FilterContentValidator validator;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() throws IOException, SAXException, URISyntaxException, InitializationError {
+ List<Object[]> result = new ArrayList<>();
+ final Path path = Paths.get(FilterContentValidatorTest.class.getResource("/filter/expected.txt").toURI());
+ final List<String> expected = Files.readAllLines(path);
+ if (expected.size() != TEST_CASE_COUNT) {
+ throw new InitializationError("Number of lines in results file must be same as test case count");
+ }
+ for (int i = 1; i <= TEST_CASE_COUNT; i++) {
+ final Document document = XmlUtil.readXmlToDocument(FilterContentValidatorTest.class.getResourceAsStream("/filter/f" + i + ".xml"));
+ result.add(new Object[]{document, expected.get(i-1)});
+ }
+ return result;
+ }
+
+ public FilterContentValidatorTest(Document filterContent, String expected) {
+ this.filterContent = XmlElement.fromDomDocument(filterContent);
+ this.expected = expected;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ List<InputStream> sources = new ArrayList<>();
+ sources.add(getClass().getResourceAsStream("/yang/filter-validator-test-mod-0.yang"));
+ sources.add(getClass().getResourceAsStream("/yang/filter-validator-test-augment.yang"));
+ SchemaContext context = parseYangSources(sources);
+ CurrentSchemaContext currentContext = mock(CurrentSchemaContext.class);
+ doReturn(context).when(currentContext).getCurrentContext();
+ validator = new FilterContentValidator(currentContext);
+ }
+
+ @Test
+ public void testValidate() throws Exception {
+ if (expected.startsWith("success")) {
+ final String expId = expected.replace("success=", "");
+ Assert.assertEquals(expId, validator.validate(filterContent).toString());
+ } else if (expected.startsWith("error")) {
+ try {
+ validator.validate(filterContent);
+ Assert.fail(XmlUtil.toString(filterContent) + " is not valid and should throw exception.");
+ } catch (Exception e) {
+ final String expectedExceptionClass = expected.replace("error=", "");
+ Assert.assertEquals(expectedExceptionClass, e.getClass().getName());
+ }
+ }
+
+ }
+
+ public static SchemaContext parseYangSources(Collection<InputStream> testFiles)
+ throws SourceException, ReactorException, FileNotFoundException {
+ CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR
+ .newBuild();
+ for (InputStream testFile : testFiles) {
+ reactor.addSource(new YangStatementSourceImpl(testFile));
+ }
+ return reactor.buildEffective();
+ }
+}
\ No newline at end of file
--- /dev/null
+error=org.opendaylight.controller.config.util.xml.DocumentedException
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/choiceList
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/maincontent
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/choiceList
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/(urn:dummy:aug?revision=1999-08-17)augmented-leaf
+error=org.opendaylight.controller.config.util.xml.DocumentedException
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <maincontent></maincontent>
+ <not-in-schema/>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <maincontent/>
+ <choiceList>
+ <choice-leaf>
+
+ </choice-leaf>
+ </choiceList>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <choiceList>
+ <choice-leaf>
+ aaa
+ </choice-leaf>
+ </choiceList>
+ <choiceList>
+ <choice-leaf>
+ bbb
+ </choice-leaf>
+ </choiceList>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <maincontent/>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <choiceList></choiceList>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <maincontent/>
+ <choiceList></choiceList>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <augmented-leaf xmlns="urn:dummy:aug"/>
+</mainroot>
\ No newline at end of file
--- /dev/null
+<mainroot xmlns="urn:dummy:mod-0">
+ <maincontent>
+ <choiceList>
+ </choiceList>
+ </maincontent>
+</mainroot>
\ No newline at end of file
--- /dev/null
+module filter-validator-test-augment {
+ namespace "urn:dummy:aug";
+ prefix "aug-0";
+ revision "1999-08-17";
+ import filter-validator-test-mod-0 {
+ prefix mod-0;
+ revision-date "2016-03-01";
+ }
+ augment "/mod-0:mainroot" {
+ leaf augmented-leaf {
+ type string;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module filter-validator-test-mod-0 {
+ namespace "urn:dummy:mod-0";
+ prefix "mod-0";
+ revision "2016-03-01";
+ container mainroot {
+ leaf maincontent {
+ mandatory true;
+ type string;
+ }
+ list choiceList {
+ key name;
+ leaf name {
+ type string;
+ }
+ choice v {
+ case a {
+ leaf choice-leaf {
+ type string;
+ }
+ }
+ case b {
+ container choice-cont {
+ leaf content {
+ type string;
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
@Override
public void close() {
-
+ for (NetconfOperation netconfOperation : netconfOperations) {
+ if (netconfOperation instanceof AutoCloseable) {
+ try {
+ ((AutoCloseable) netconfOperation).close();
+ } catch (Exception e) {
+ throw new IllegalStateException("Exception while closing " + netconfOperation, e);
+ }
+ }
+ }
}
}
+++ /dev/null
-/*
- * Copyright (c) 2013 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.impl;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.opendaylight.netconf.util.messages.NetconfMessageHeader;
-
-@Deprecated
-public class MessageHeaderTest {
- @Test
- public void testFromBytes() {
- final byte[] raw = new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x35, (byte) 0x38, (byte) 0x0a };
- NetconfMessageHeader header = NetconfMessageHeader.fromBytes(raw);
- assertEquals(58, header.getLength());
- }
-
- @Test
- public void testToBytes() {
- NetconfMessageHeader header = new NetconfMessageHeader(123);
- assertArrayEquals(new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x0a },
- header.toBytes());
- }
-}
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import com.google.common.base.Charsets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
+import java.nio.ByteBuffer;
import java.util.Queue;
import org.junit.Before;
import org.junit.Test;
+import org.opendaylight.netconf.api.NetconfMessage;
import org.opendaylight.netconf.nettyutil.handler.ChunkedFramingMechanismEncoder;
import org.opendaylight.netconf.nettyutil.handler.FramingMechanismHandlerFactory;
import org.opendaylight.netconf.nettyutil.handler.NetconfChunkAggregator;
import org.opendaylight.netconf.nettyutil.handler.NetconfXMLToMessageDecoder;
import org.opendaylight.netconf.util.messages.FramingMechanism;
import org.opendaylight.netconf.util.messages.NetconfMessageConstants;
-import org.opendaylight.netconf.util.messages.NetconfMessageHeader;
import org.opendaylight.netconf.util.test.XmlFileLoader;
-import org.opendaylight.netconf.api.NetconfMessage;
public class MessageParserTest {
byte[] header = new byte[String.valueOf(exptHeaderLength).length()
+ NetconfMessageConstants.MIN_HEADER_LENGTH - 1];
recievedOutbound.getBytes(0, header);
- NetconfMessageHeader messageHeader = NetconfMessageHeader.fromBytes(header);
- assertEquals(exptHeaderLength, messageHeader.getLength());
+ assertEquals(exptHeaderLength, getHeaderLength(header));
testChunkChannel.writeInbound(recievedOutbound);
}
assertNotNull(receivedMessage);
assertXMLEqual(this.msg.getDocument(), receivedMessage.getDocument());
}
+
+ private static long getHeaderLength(byte[] bytes) {
+ byte[] HEADER_START = new byte[] { (byte) 0x0a, (byte) 0x23 };
+ return Long.parseLong(Charsets.US_ASCII.decode(
+ ByteBuffer.wrap(bytes, HEADER_START.length, bytes.length - HEADER_START.length - 1)).toString());
+ }
}
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-parser-impl</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>yang-jmx-generator-plugin</artifactId>
- </dependency>
<dependency>
<groupId>org.opendaylight.netconf</groupId>
<artifactId>sal-netconf-connector</artifactId>
RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
RemoteDeviceHandler<NetconfSessionPreferences> salFacade =
- createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker, defaultRequestTimeoutMillis);
+ createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker);
if (keepaliveDelay > 0) {
LOG.warn("Adding keepalive facade, for device {}", nodeId);
- salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay);
+ salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis);
}
final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
.build();
}
- protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis);
+ protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker);
@Override
public abstract ConnectionStatusListenerRegistration registerConnectionStatusListener(NodeId node, RemoteDeviceHandler<NetconfSessionPreferences> listener);
RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
RemoteDeviceHandler<NetconfSessionPreferences> salFacade =
- createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker, defaultRequestTimeoutMillis);
+ createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker);
if (keepaliveDelay > 0) {
LOG.warn("Adding keepalive facade, for device {}", nodeId);
- salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay);
+ salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis);
}
final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
}
@Override
- protected RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) {
- return new TopologyMountPointFacade(topologyId, id, domBroker, bindingBroker, defaultRequestTimeoutMillis);
+ protected RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker) {
+ return new TopologyMountPointFacade(topologyId, id, domBroker, bindingBroker);
}
@Override
}
@Override
- protected RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) {
- return new NetconfDeviceSalFacade(id, domBroker, bindingAwareBroker, defaultRequestTimeoutMillis);
+ protected RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker) {
+ return new NetconfDeviceSalFacade(id, domBroker, bindingAwareBroker);
}
@Override
public NetconfDeviceMasterDataBroker(final ActorSystem actorSystem, final RemoteDeviceId id,
final SchemaContext schemaContext, final DOMRpcService rpc,
- final NetconfSessionPreferences netconfSessionPreferences, final long requestTimeoutMillis) {
+ final NetconfSessionPreferences netconfSessionPreferences) {
this.id = id;
- delegateBroker = new NetconfDeviceDataBroker(id, schemaContext, rpc, netconfSessionPreferences, requestTimeoutMillis);
+ delegateBroker = new NetconfDeviceDataBroker(id, schemaContext, rpc, netconfSessionPreferences);
this.actorSystem = actorSystem;
// only ever need 1 readTx since it doesnt need to be closed
private final RemoteDeviceId id;
private final Broker domBroker;
private final BindingAwareBroker bindingBroker;
- private final long defaultRequestTimeoutMillis;
private SchemaContext remoteSchemaContext = null;
private NetconfSessionPreferences netconfSessionPreferences = null;
public TopologyMountPointFacade(final String topologyId,
final RemoteDeviceId id,
final Broker domBroker,
- final BindingAwareBroker bindingBroker,
- long defaultRequestTimeoutMillis) {
+ final BindingAwareBroker bindingBroker) {
this.topologyId = topologyId;
this.id = id;
this.domBroker = domBroker;
this.bindingBroker = bindingBroker;
- this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis;
this.salProvider = new ClusteredNetconfDeviceMountInstanceProxy(id);
registerToSal(domBroker);
}
deviceDataBroker = TypedActor.get(context).typedActorOf(new TypedProps<>(ProxyNetconfDeviceDataBroker.class, new Creator<NetconfDeviceMasterDataBroker>() {
@Override
public NetconfDeviceMasterDataBroker create() throws Exception {
- return new NetconfDeviceMasterDataBroker(actorSystem, id, remoteSchemaContext, deviceRpc, netconfSessionPreferences, defaultRequestTimeoutMillis);
+ return new NetconfDeviceMasterDataBroker(actorSystem, id, remoteSchemaContext, deviceRpc, netconfSessionPreferences);
}
}), MOUNT_POINT);
LOG.debug("Master data broker registered on path {}", TypedActor.get(actorSystem).getActorRefFor(deviceDataBroker).path());
}
@Override
- protected RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) {
+ protected RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id, Broker domBroker, BindingAwareBroker bindingBroker) {
return salFacade;
}
MockitoAnnotations.initMocks(this);
- mountPointFacade = new TopologyMountPointFacade(TOPOLOGY_ID, REMOTE_DEVICE_ID, domBroker, bindingBroker, 1L);
+ mountPointFacade = new TopologyMountPointFacade(TOPOLOGY_ID, REMOTE_DEVICE_ID, domBroker, bindingBroker);
mountPointFacade.registerConnectionStatusListener(connectionStatusListener1);
mountPointFacade.registerConnectionStatusListener(connectionStatusListener2);
+++ /dev/null
-/*
- * Copyright (c) 2013 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.util.messages;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Preconditions;
-import java.nio.ByteBuffer;
-
-/**
- * Netconf message header is used only when chunked framing mechanism is
- * supported. The header consists of only the length field.
- */
-@Deprecated
-public final class NetconfMessageHeader {
- // \n#<length>\n
- private static final byte[] HEADER_START = new byte[] { (byte) 0x0a, (byte) 0x23 };
- private static final byte HEADER_END = (byte) 0x0a;
- private final long length;
-
- public NetconfMessageHeader(final long length) {
- Preconditions.checkArgument(length < Integer.MAX_VALUE && length > 0);
- this.length = length;
- }
-
- public byte[] toBytes() {
- return toBytes(this.length);
- }
-
- // FIXME: improve precision to long
- public int getLength() {
- return (int) this.length;
- }
-
- public static NetconfMessageHeader fromBytes(final byte[] bytes) {
- // the length is variable therefore bytes between headerBegin and
- // headerEnd mark the length
- // the length should be only numbers and therefore easily parsed with
- // ASCII
- long length = Long.parseLong(Charsets.US_ASCII.decode(
- ByteBuffer.wrap(bytes, HEADER_START.length, bytes.length - HEADER_START.length - 1)).toString());
-
- return new NetconfMessageHeader(length);
- }
-
- public static byte[] toBytes(final long length) {
- final byte[] l = String.valueOf(length).getBytes(Charsets.US_ASCII);
- final byte[] h = new byte[HEADER_START.length + l.length + 1];
- System.arraycopy(HEADER_START, 0, h, 0, HEADER_START.length);
- System.arraycopy(l, 0, h, HEADER_START.length, l.length);
- System.arraycopy(new byte[] { HEADER_END }, 0, h, HEADER_START.length + l.length, 1);
- return h;
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2014 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.util.messages;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import com.google.common.base.Charsets;
-import org.junit.Test;
-
-@Deprecated
-public class NetconfMessageHeaderTest {
- @Test
- public void testGet() throws Exception {
- NetconfMessageHeader header = new NetconfMessageHeader(10);
- assertEquals(header.getLength(), 10);
-
- byte[] expectedValue = "\n#10\n".getBytes(Charsets.US_ASCII);
- assertArrayEquals(expectedValue, header.toBytes());
- }
-}
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</dependency>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-api-mockito</artifactId>
- <version>1.6.4</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-module-junit4</artifactId>
- <version>1.6.4</version>
- <scope>test</scope>
- </dependency>
</dependencies>
<scm>
final ExecutorService globalProcessingExecutor = processingExecutor.getExecutor();
RemoteDeviceHandler<NetconfSessionPreferences> salFacade
- = new NetconfDeviceSalFacade(id, domRegistry, bindingRegistry, getDefaultRequestTimeoutMillis());
+ = new NetconfDeviceSalFacade(id, domRegistry, bindingRegistry);
final Long keepaliveDelay = getKeepaliveDelay();
if (shouldSendKeepalive()) {
// Keepalive executor is optional for now and a default instance is supported
final ScheduledExecutorService executor = keepaliveExecutor == null ? DEFAULT_KEEPALIVE_EXECUTOR : keepaliveExecutor.getExecutor();
- salFacade = new KeepaliveSalFacade(id, salFacade, executor, keepaliveDelay);
+ salFacade = new KeepaliveSalFacade(id, salFacade, executor, keepaliveDelay, getDefaultRequestTimeoutMillis());
}
// Setup information related to the SchemaRegistry, SchemaResourceFactory, etc.
*/
private static NetconfStateSchemas create(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id) {
if(remoteSessionCapabilities.isMonitoringSupported() == false) {
+ // TODO - need to search for get-schema support, not just ietf-netconf-monitoring support
+ // issue might be a deviation to ietf-netconf-monitoring where get-schema is unsupported...
LOG.warn("{}: Netconf monitoring not supported on device, cannot detect provided schemas", id);
return EMPTY;
}
import java.net.URI;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
import org.opendaylight.netconf.client.NetconfClientSession;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
return nonModuleCaps;
}
+ // allows partial matches - assuming parameters are in the same order
+ public boolean containsPartialNonModuleCapability(final String capability) {
+ final Iterator<String> iterator = nonModuleCaps.iterator();
+ while(iterator.hasNext()) {
+ if (iterator.next().startsWith(capability)) {
+ LOG.trace("capability {} partially matches {}", capability, nonModuleCaps);
+ return true;
+ }
+ }
+ return false;
+ }
+
public boolean containsNonModuleCapability(final String capability) {
return nonModuleCaps.contains(capability);
}
}
public boolean isNotificationsSupported() {
- return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_NOTIFICATONS_URI.toString())
+ return containsPartialNonModuleCapability(NetconfMessageTransformUtil.NETCONF_NOTIFICATONS_URI.toString())
|| containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS);
}
public boolean isMonitoringSupported() {
return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)
- || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
+ || containsPartialNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
}
/**
// 2 minutes keepalive delay by default
private static final long DEFAULT_DELAY = TimeUnit.MINUTES.toSeconds(2);
+ // 1 minute transaction timeout by default
+ private static final long DEFAULT_TRANSACTION_TIMEOUT_MILLI = TimeUnit.MILLISECONDS.toMillis(60000);
+
private final RemoteDeviceId id;
private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
private final ScheduledExecutorService executor;
private final long keepaliveDelaySeconds;
private final ResetKeepalive resetKeepaliveTask;
+ private final long defaultRequestTimeoutMillis;
private volatile NetconfDeviceCommunicator listener;
private volatile ScheduledFuture<?> currentKeepalive;
private volatile DOMRpcService currentDeviceRpc;
public KeepaliveSalFacade(final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
- final ScheduledExecutorService executor, final long keepaliveDelaySeconds) {
+ final ScheduledExecutorService executor, final long keepaliveDelaySeconds, final long defaultRequestTimeoutMillis) {
this.id = id;
this.salFacade = salFacade;
this.executor = executor;
this.keepaliveDelaySeconds = keepaliveDelaySeconds;
+ this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis;
this.resetKeepaliveTask = new ResetKeepalive();
}
public KeepaliveSalFacade(final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
final ScheduledExecutorService executor) {
- this(id, salFacade, executor, DEFAULT_DELAY);
+ this(id, salFacade, executor, DEFAULT_DELAY, DEFAULT_TRANSACTION_TIMEOUT_MILLI);
}
/**
@Override
public void onDeviceConnected(final SchemaContext remoteSchemaContext, final NetconfSessionPreferences netconfSessionPreferences, final DOMRpcService deviceRpc) {
this.currentDeviceRpc = deviceRpc;
- final DOMRpcService deviceRpc1 = new KeepaliveDOMRpcService(deviceRpc, resetKeepaliveTask);
+ final DOMRpcService deviceRpc1 = new KeepaliveDOMRpcService(deviceRpc, resetKeepaliveTask, defaultRequestTimeoutMillis, executor);
salFacade.onDeviceConnected(remoteSchemaContext, netconfSessionPreferences, deviceRpc1);
LOG.debug("{}: Netconf session initiated, starting keepalives", id);
/**
* Reset keepalive after each RPC response received
*/
- private class ResetKeepalive implements com.google.common.util.concurrent.FutureCallback<DOMRpcResult> {
+ private class ResetKeepalive implements FutureCallback<DOMRpcResult> {
@Override
public void onSuccess(@Nullable final DOMRpcResult result) {
// No matter what response we got, rpc-reply or rpc-error, we got it from device so the netconf session is OK
@Override
public void onFailure(@Nonnull final Throwable t) {
- // User/Application RPC failed (The RPC did not reach the remote device.
+ // User/Application RPC failed (The RPC did not reach the remote device or .. TODO what other reasons could cause this ?)
// There is no point in keeping this session. Reconnect.
LOG.warn("{}: Rpc failure detected. Reconnecting netconf session", id, t);
reconnect();
}
}
+ /*
+ * Request timeout task is called once the defaultRequestTimeoutMillis is
+ * reached. At this moment, if the request is not yet finished, we cancel
+ * it.
+ */
+ private static final class RequestTimeoutTask implements Runnable {
+
+ private final CheckedFuture<DOMRpcResult, DOMRpcException> rpcResultFuture;
+
+ public RequestTimeoutTask(final CheckedFuture<DOMRpcResult, DOMRpcException> rpcResultFuture) {
+ this.rpcResultFuture = rpcResultFuture;
+ }
+
+ @Override
+ public void run() {
+ if (!rpcResultFuture.isDone()) {
+ rpcResultFuture.cancel(true);
+ }
+ }
+ }
+
/**
- * DOMRpcService proxy that attaches reset-keepalive-task to each RPC invocation.
+ * DOMRpcService proxy that attaches reset-keepalive-task and schedule
+ * request-timeout-task to each RPC invocation.
*/
private static final class KeepaliveDOMRpcService implements DOMRpcService {
private final DOMRpcService deviceRpc;
private ResetKeepalive resetKeepaliveTask;
+ private final long defaultRequestTimeoutMillis;
+ private final ScheduledExecutorService executor;
- public KeepaliveDOMRpcService(final DOMRpcService deviceRpc, final ResetKeepalive resetKeepaliveTask) {
+ public KeepaliveDOMRpcService(final DOMRpcService deviceRpc, final ResetKeepalive resetKeepaliveTask,
+ final long defaultRequestTimeoutMillis, final ScheduledExecutorService executor) {
this.deviceRpc = deviceRpc;
this.resetKeepaliveTask = resetKeepaliveTask;
+ this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis;
+ this.executor = executor;
}
@Nonnull
public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(@Nonnull final SchemaPath type, final NormalizedNode<?, ?> input) {
final CheckedFuture<DOMRpcResult, DOMRpcException> domRpcResultDOMRpcExceptionCheckedFuture = deviceRpc.invokeRpc(type, input);
Futures.addCallback(domRpcResultDOMRpcExceptionCheckedFuture, resetKeepaliveTask);
+
+ final RequestTimeoutTask timeoutTask = new RequestTimeoutTask(domRpcResultDOMRpcExceptionCheckedFuture);
+ executor.schedule(timeoutTask, defaultRequestTimeoutMillis, TimeUnit.MILLISECONDS);
+
return domRpcResultDOMRpcExceptionCheckedFuture;
}
public final class NetconfDeviceDataBroker implements DOMDataBroker {
private final RemoteDeviceId id;
private final NetconfBaseOps netconfOps;
- private final long requestTimeoutMillis;
private final boolean rollbackSupport;
private boolean candidateSupported;
private boolean runningWritable;
- public NetconfDeviceDataBroker(final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, final NetconfSessionPreferences netconfSessionPreferences, long requestTimeoutMillis) {
+ public NetconfDeviceDataBroker(final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, final NetconfSessionPreferences netconfSessionPreferences) {
this.id = id;
this.netconfOps = new NetconfBaseOps(rpc, schemaContext);
- this.requestTimeoutMillis = requestTimeoutMillis;
// get specific attributes from netconf preferences and get rid of it
// no need to keep the entire preferences object, its quite big with all the capability QNames
candidateSupported = netconfSessionPreferences.isCandidateSupported();
@Override
public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
- return new ReadOnlyTx(netconfOps, id, requestTimeoutMillis);
+ return new ReadOnlyTx(netconfOps, id);
}
@Override
public DOMDataWriteTransaction newWriteOnlyTransaction() {
if(candidateSupported) {
if(runningWritable) {
- return new WriteCandidateRunningTx(id, netconfOps, rollbackSupport, requestTimeoutMillis);
+ return new WriteCandidateRunningTx(id, netconfOps, rollbackSupport);
} else {
- return new WriteCandidateTx(id, netconfOps, rollbackSupport, requestTimeoutMillis);
+ return new WriteCandidateTx(id, netconfOps, rollbackSupport);
}
} else {
- return new WriteRunningTx(id, netconfOps, rollbackSupport, requestTimeoutMillis);
+ return new WriteRunningTx(id, netconfOps, rollbackSupport);
}
}
private final RemoteDeviceId id;
private final NetconfDeviceSalProvider salProvider;
- private final long defaultRequestTimeoutMillis;
private final List<AutoCloseable> salRegistrations = Lists.newArrayList();
- public NetconfDeviceSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, long defaultRequestTimeoutMillis) {
+ public NetconfDeviceSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker) {
this.id = id;
this.salProvider = new NetconfDeviceSalProvider(id);
- this.defaultRequestTimeoutMillis = defaultRequestTimeoutMillis;
registerToSal(domBroker, bindingBroker);
}
public synchronized void onDeviceConnected(final SchemaContext schemaContext,
final NetconfSessionPreferences netconfSessionPreferences, final DOMRpcService deviceRpc) {
- final DOMDataBroker domBroker = new NetconfDeviceDataBroker(id, schemaContext, deviceRpc, netconfSessionPreferences, defaultRequestTimeoutMillis);
+ final DOMDataBroker domBroker = new NetconfDeviceDataBroker(id, schemaContext, deviceRpc, netconfSessionPreferences);
final NetconfDeviceNotificationService notificationService = new NetconfDeviceNotificationService();
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger LOG = LoggerFactory.getLogger(AbstractWriteTx.class);
- protected final long defaultRequestTimeoutMillis;
protected final RemoteDeviceId id;
protected final NetconfBaseOps netOps;
protected final boolean rollbackSupport;
// Allow commit to be called only once
protected boolean finished = false;
- public AbstractWriteTx(final long requestTimeoutMillis, final NetconfBaseOps netOps, final RemoteDeviceId id, final boolean rollbackSupport) {
- this.defaultRequestTimeoutMillis = requestTimeoutMillis;
+ public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final boolean rollbackSupport) {
this.netOps = netOps;
this.id = id;
this.rollbackSupport = rollbackSupport;
}
protected abstract void editConfig(DataContainerChild<?, ?> editStructure, Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException;
-
-
- protected ListenableFuture<DOMRpcResult> perfomRequestWithTimeout(String operation, ListenableFuture<DOMRpcResult> future) {
- try {
- future.get(defaultRequestTimeoutMillis, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException e) {
- LOG.error("{}: {} failed with error", operation, id, e);
- return Futures.immediateFailedCheckedFuture(new RuntimeException(id + ": " + operation + " failed"));
- } catch (TimeoutException e) {
- LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, defaultRequestTimeoutMillis, e);
- return Futures.immediateFailedCheckedFuture(new SchemaSourceException(e.getMessage()));
- }
- return future;
- }
}
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
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.DOMDataReadOnlyTransaction;
private final RemoteDeviceId id;
private final FutureCallback<DOMRpcResult> loggingCallback;
- private final long requestTimeoutMillis;
-
- public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id, final long requestTimeoutMillis) {
+ public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id) {
this.netconfOps = netconfOps;
this.id = id;
- this.requestTimeoutMillis = requestTimeoutMillis;
// Simple logging callback to log result of read operation
loggingCallback = new FutureCallback<DOMRpcResult>() {
}
});
- if(!readWithTimeout("readConfigurationData", configRunning)) {
- return null;
- }
-
return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
}
}
});
- if(!readWithTimeout("readOperationalData", configCandidate)) {
- return null;
- }
-
return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
}
public Object getIdentifier() {
return this;
}
-
- private boolean readWithTimeout(String operation, ListenableFuture<DOMRpcResult> future) {
- try {
- future.get(requestTimeoutMillis, TimeUnit.MILLISECONDS);
- } catch (InterruptedException | ExecutionException e) {
- LOG.error("{}: {} failed with error", id, operation, e);
- throw new RuntimeException(id + ": readOperationalData failed");
- } catch (TimeoutException e) {
- LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, requestTimeoutMillis, e);
- future.cancel(true);
- return false;
- }
- return true;
- }
}
private static final Logger LOG = LoggerFactory.getLogger(WriteCandidateRunningTx.class);
- public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final boolean rollbackSupport, final long requestTimeoutMillis) {
- super(id, netOps, rollbackSupport, requestTimeoutMillis);
+ public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final boolean rollbackSupport) {
+ super(id, netOps, rollbackSupport);
}
@Override
}
private void lockRunning() {
- final String operation = "Lock Running";
try {
- invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+ invokeBlocking("Lock running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
@Override
public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
- return perfomRequestWithTimeout(operation, input.lockRunning(new NetconfRpcFutureCallback(operation, id)));
-
+ return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
}
});
} catch (final NetconfDocumentedException e) {
}
};
- public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final boolean rollbackSupport, long requestTimeoutMillis) {
- super(requestTimeoutMillis, rpc, id, rollbackSupport);
+ public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final boolean rollbackSupport) {
+ super(rpc, id, rollbackSupport);
}
@Override
}
private void lock() throws NetconfDocumentedException {
- final String operation = "Lock candidate";
try {
- invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+ invokeBlocking("Lock candidate", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
@Override
public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
- return perfomRequestWithTimeout(operation, input.lockCandidate(new NetconfRpcFutureCallback(operation, id)));
+ return input.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id));
}
});
} catch (final NetconfDocumentedException e) {
@Override
protected void editConfig(final DataContainerChild<?, ?> editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
- final String operation = "Edit candidate";
- invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+ invokeBlocking("Edit candidate", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
@Override
public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
-
- return perfomRequestWithTimeout(operation, defaultOperation.isPresent()
- ? input.editConfigCandidate(new NetconfRpcFutureCallback(operation, id), editStructure, defaultOperation.get(),
- rollbackSupport)
- : input.editConfigCandidate(new NetconfRpcFutureCallback(operation, id), editStructure,
- rollbackSupport));
+ return defaultOperation.isPresent()
+ ? input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, defaultOperation.get(),
+ rollbackSupport)
+ : input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure,
+ rollbackSupport);
}
});
}
private static final Logger LOG = LoggerFactory.getLogger(WriteRunningTx.class);
public WriteRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps,
- final boolean rollbackSupport, long requestTimeoutMillis) {
- super(requestTimeoutMillis, netOps, id, rollbackSupport);
+ final boolean rollbackSupport) {
+ super(netOps, id, rollbackSupport);
}
@Override
}
private void lock() {
- final String operation = "Lock running";
try {
- invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+ invokeBlocking("Lock running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
@Override
public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
- return perfomRequestWithTimeout(operation, input.lockRunning(new NetconfRpcFutureCallback(operation, id)));
+ return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
}
});
} catch (final NetconfDocumentedException e) {
@Override
public synchronized CheckedFuture<Void, TransactionCommitFailedException> submit() {
- final ListenableFuture<Void> commitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
+ final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
@Override
public Void apply(final RpcResult<TransactionStatus> input) {
return null;
}
});
- return Futures.makeChecked(commitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
+ return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
@Override
public TransactionCommitFailedException apply(final Exception input) {
return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
@Override
protected void editConfig(final DataContainerChild<?, ?> editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
- final String operation = "Edit running";
- invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+ invokeBlocking("Edit running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
@Override
public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
- return perfomRequestWithTimeout(operation, defaultOperation.isPresent()
- ? input.editConfigRunning(new NetconfRpcFutureCallback(operation, id), editStructure, defaultOperation.get(),
- rollbackSupport)
- : input.editConfigRunning(new NetconfRpcFutureCallback(operation, id), editStructure,
- rollbackSupport));
+ return defaultOperation.isPresent()
+ ? input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, defaultOperation.get(),
+ rollbackSupport)
+ : input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure,
+ rollbackSupport);
}
});
}
private void unlock() {
- final String operation = "Unlocking running";
try {
- invokeBlocking(operation, new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
+ invokeBlocking("Unlocking running", new Function<NetconfBaseOps, ListenableFuture<DOMRpcResult>>() {
@Override
public ListenableFuture<DOMRpcResult> apply(final NetconfBaseOps input) {
- return perfomRequestWithTimeout(operation, input.unlockRunning(new NetconfRpcFutureCallback(operation, id)));
+ return input.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
}
});
} catch (final NetconfDocumentedException e) {
doReturn(Futures.immediateCheckedFuture(result)).when(deviceRpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
final KeepaliveSalFacade keepaliveSalFacade =
- new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L);
+ new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L, 1L);
keepaliveSalFacade.setListener(listener);
keepaliveSalFacade.onDeviceConnected(null, null, deviceRpc);
.when(deviceRpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
final KeepaliveSalFacade keepaliveSalFacade =
- new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L);
+ new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 1L, 1L);
keepaliveSalFacade.setListener(listener);
keepaliveSalFacade.onDeviceConnected(null, null, deviceRpc);
.when(deviceRpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
final KeepaliveSalFacade keepaliveSalFacade =
- new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 100L);
+ new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, executorServiceSpy, 100L, 1L);
keepaliveSalFacade.setListener(listener);
keepaliveSalFacade.onDeviceConnected(null, null, deviceRpc);
private NetconfDeviceDataBroker getDataBroker(String... caps) {
NetconfSessionPreferences prefs = NetconfSessionPreferences.fromStrings(Arrays.asList(caps));
final RemoteDeviceId id = new RemoteDeviceId("device-1", InetSocketAddress.createUnresolved("localhost", 17830));
- return new NetconfDeviceDataBroker(id, schemaContext, rpcService, prefs, 1000);
+ return new NetconfDeviceDataBroker(id, schemaContext, rpcService, prefs);
}
}
\ No newline at end of file
@Test
public void testIgnoreNonVisibleData() {
final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, mock(SchemaContext.class)),
- false, 60000L);
+ false);
final MapNode emptyList = ImmutableNodes.mapNodeBuilder(NETCONF_FILTER_QNAME).build();
tx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(NETCONF_FILTER_QNAME)), emptyList);
tx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(NETCONF_FILTER_QNAME)), emptyList);
@Test
public void testDiscardChanges() {
final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, mock(SchemaContext.class)),
- false, 60000L);
+ false);
final CheckedFuture<Void, TransactionCommitFailedException> submitFuture = tx.submit();
try {
submitFuture.checkedGet();
.doReturn(rpcErrorFuture).when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, mock(SchemaContext.class)),
- false, 60000L);
+ false);
final CheckedFuture<Void, TransactionCommitFailedException> submitFuture = tx.submit();
try {
.when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc, NetconfMessageTransformer.BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS.getSchemaContext()),
- false, 60000L);
+ false);
try {
tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
} catch (final Exception e) {
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
import java.net.InetSocketAddress;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
-import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-@PrepareForTest({NetconfBaseOps.class})
-@RunWith(PowerMockRunner.class)
public class ReadOnlyTxTest {
private static final YangInstanceIdentifier path = YangInstanceIdentifier.create();
public void testRead() throws Exception {
final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc, mock(SchemaContext.class));
- final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)), 60000L);
+ final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)));
readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create());
verify(rpc).invokeRpc(Mockito.eq(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME)), any(NormalizedNode.class));
readOnlyTx.read(LogicalDatastoreType.OPERATIONAL, path);
verify(rpc).invokeRpc(Mockito.eq(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.NETCONF_GET_QNAME)), any(NormalizedNode.class));
}
-
- @SuppressWarnings("unchecked")
- @Test
- public void testReadTimeout() throws Exception {
- final ListenableFuture<DOMRpcResult> future = mock(ListenableFuture.class);
-
- Mockito.when(future.get(Mockito.anyLong(), any(TimeUnit.class))).then(new Answer<DOMRpcResult>() {
- @Override
- public DOMRpcResult answer(InvocationOnMock invocation)
- throws Throwable {
- throw new TimeoutException("Processing Timeout");
- }
- });
-
- final NetconfBaseOps netconfOps = PowerMockito.mock(NetconfBaseOps.class);
- Mockito.when(netconfOps.getConfigRunning(any(FutureCallback.class), any(Optional.class))).thenReturn(future);
-
-
- final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)), 100L);
- Assert.assertNull("Read operation didn't correctly timeout", readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create()));
- readOnlyTx.close();
- }
}
\ No newline at end of file
final ExecutorService executorService = Executors.newFixedThreadPool(threadAmount);
LOG.info("Starting performance test");
+ boolean allThreadsCompleted = true;
final Stopwatch started = Stopwatch.createStarted();
try {
final List<Future<Void>> futures = executorService.invokeAll(callables, parameters.timeout, TimeUnit.MINUTES);
for (int i = 0; i < futures.size(); i++) {
Future<Void> future = futures.get(i);
if (future.isCancelled()) {
+ allThreadsCompleted = false;
LOG.info("{}. thread timed out.", i + 1);
} else {
try {
future.get();
} catch (final ExecutionException e) {
+ allThreadsCompleted = false;
LOG.info("{}. thread failed.", i + 1, e);
}
}
}
} catch (final InterruptedException e) {
+ allThreadsCompleted = false;
LOG.warn("Unable to execute requests", e);
}
executorService.shutdownNow();
started.stop();
LOG.info("FINISHED. Execution time: {}", started);
- LOG.info("Requests per second: {}", (parameters.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS)));
-
+ // If some threads failed or timed out, skip calculation of requests per second value
+ // and do not log it
+ if(allThreadsCompleted) {
+ LOG.info("Requests per second: {}", (parameters.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS)));
+ }
System.exit(0);
}
<dependency>
<groupId>net.java.dev.stax-utils</groupId>
<artifactId>stax-utils</artifactId>
- <version>20070216</version>
</dependency>
<dependency>
org.opendaylight.aaa.filterchain.filters,
org.apache.shiro.web.env
</Import-Package>
- <Embed-Dependency>stax-utils</Embed-Dependency>
<Web-ContextPath>/restconf</Web-ContextPath>
</instructions>
</configuration>
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.impl.PATCH;
import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
/**
* The URI hierarchy for the RESTCONF resources consists of an entry point container, 4 top-level resources, and 1
MediaType.APPLICATION_XML, MediaType.TEXT_XML })
public NormalizedNodeContext getAvailableStreams(@Context UriInfo uriInfo);
+ @PATCH
+ @Path("/config/{identifier:.+}")
+ @Consumes({MediaTypes.PATCH + JSON, MediaTypes.PATCH + XML})
+ @Produces({MediaTypes.PATCH_STATUS + JSON, MediaTypes.PATCH_STATUS + XML})
+ PATCHStatusContext patchConfigurationData(@Encoded @PathParam("identifier") String identifier, PATCHContext
+ context, @Context UriInfo uriInfo);
+
+ @PATCH
+ @Path("/config")
+ @Consumes({MediaTypes.PATCH + JSON, MediaTypes.PATCH + XML})
+ @Produces({MediaTypes.PATCH_STATUS + JSON, MediaTypes.PATCH_STATUS + XML})
+ PATCHStatusContext patchConfigurationData(PATCHContext context, @Context UriInfo uriInfo);
}
--- /dev/null
+/*
+ * Copyright (c) 2015 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.sal.rest.impl;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.rest.api.Draft02;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
+import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+@Consumes({Draft02.MediaTypes.PATCH + RestconfService.JSON})
+public class JsonToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader<PATCHContext> {
+
+ private final static Logger LOG = LoggerFactory.getLogger(JsonToPATCHBodyReader.class);
+ private String patchId;
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public PATCHContext readFrom(Class<PATCHContext> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
+ try {
+ return readFrom(getInstanceIdentifierContext(), entityStream);
+ } catch (final Exception e) {
+ throw propagateExceptionAs(e);
+ }
+ }
+
+ private static RuntimeException propagateExceptionAs(Exception e) throws RestconfDocumentedException {
+ if(e instanceof RestconfDocumentedException) {
+ throw (RestconfDocumentedException)e;
+ }
+
+ if(e instanceof ResultAlreadySetException) {
+ LOG.debug("Error parsing json input:", e);
+
+ throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+ }
+
+ throw new RestconfDocumentedException("Error parsing json input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+
+ public PATCHContext readFrom(final String uriPath, final InputStream entityStream) throws
+ RestconfDocumentedException {
+ try {
+ return readFrom(ControllerContext.getInstance().toInstanceIdentifier(uriPath), entityStream);
+ } catch (final Exception e) {
+ propagateExceptionAs(e);
+ return null; // no-op
+ }
+ }
+
+ private PATCHContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream) throws IOException {
+ if (entityStream.available() < 1) {
+ return new PATCHContext(path, null, null);
+ }
+
+ final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream));
+ final List<PATCHEntity> resultList = read(jsonReader, path);
+ jsonReader.close();
+
+ return new PATCHContext(path, resultList, patchId);
+ }
+
+ private List<PATCHEntity> read(final JsonReader in, InstanceIdentifierContext path) throws
+ IOException {
+
+ boolean inEdit = false;
+ boolean inValue = false;
+ String operation = null;
+ String target = null;
+ String editId = null;
+ List<PATCHEntity> resultCollection = new ArrayList<>();
+
+ while (in.hasNext()) {
+ switch (in.peek()) {
+ case STRING:
+ case NUMBER:
+ in.nextString();
+ break;
+ case BOOLEAN:
+ Boolean.toString(in.nextBoolean());
+ break;
+ case NULL:
+ in.nextNull();
+ break;
+ case BEGIN_ARRAY:
+ in.beginArray();
+ break;
+ case BEGIN_OBJECT:
+ if (inEdit && operation != null & target != null & inValue) {
+ //let's do the stuff - find out target node
+// StringInstanceIdentifierCodec codec = new StringInstanceIdentifierCodec(path
+// .getSchemaContext());
+// if (path.getInstanceIdentifier().toString().equals("/")) {
+// final YangInstanceIdentifier deserialized = codec.deserialize(target);
+// }
+ DataSchemaNode targetNode = ((DataNodeContainer)(path.getSchemaNode())).getDataChildByName
+ (target.replace("/", ""));
+ if (targetNode == null) {
+ LOG.debug("Target node {} not found in path {} ", target, path.getSchemaNode());
+ throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE);
+ } else {
+
+ final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+
+ //keep on parsing json from place where target points
+ final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(),
+ path.getSchemaNode());
+ jsonParser.parse(in);
+
+ final YangInstanceIdentifier targetII = path.getInstanceIdentifier().node(targetNode.getQName());
+ resultCollection.add(new PATCHEntity(editId, operation, targetII, resultHolder.getResult
+ ()));
+ inValue = false;
+
+ operation = null;
+ target = null;
+ }
+ in.endObject();
+ } else {
+ in.beginObject();
+ }
+ break;
+ case END_DOCUMENT:
+ break;
+ case NAME:
+ final String name = in.nextName();
+
+ switch (name) {
+ case "edit" : inEdit = true;
+ break;
+ case "operation" : operation = in.nextString();
+ break;
+ case "target" : target = in.nextString();
+ break;
+ case "value" : inValue = true;
+ break;
+ case "patch-id" : patchId = in.nextString();
+ break;
+ case "edit-id" : editId = in.nextString();
+ break;
+ }
+ break;
+ case END_OBJECT:
+ in.endObject();
+ break;
+ case END_ARRAY:
+ in.endArray();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return ImmutableList.copyOf(resultCollection);
+ }
+
+ private class StringInstanceIdentifierCodec extends AbstractStringInstanceIdentifierCodec {
+
+ private final DataSchemaContextTree dataContextTree;
+ private final SchemaContext context;
+
+ StringInstanceIdentifierCodec(SchemaContext context) {
+ this.context = Preconditions.checkNotNull(context);
+ this.dataContextTree = DataSchemaContextTree.from(context);
+ }
+
+ @Nonnull
+ @Override
+ protected DataSchemaContextTree getDataContextTree() {
+ return dataContextTree;
+ }
+
+ @Nullable
+ @Override
+ protected String prefixForNamespace(@Nonnull URI namespace) {
+ final Module module = context.findModuleByNamespaceAndRevision(namespace, null);
+ return module == null ? null : module.getName();
+ }
+
+ @Nullable
+ @Override
+ protected QName createQName(@Nonnull String prefix, @Nonnull String localName) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ }
+}
--- /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.sal.rest.impl;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.ws.rs.HttpMethod;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@HttpMethod("PATCH")
+@Documented
+public @interface PATCH {
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2015 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.sal.rest.impl;
+
+import com.google.common.base.Charsets;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+
+@Provider
+@Produces({MediaTypes.PATCH_STATUS + RestconfService.JSON})
+public class PATCHJsonBodyWriter implements MessageBodyWriter<PATCHStatusContext> {
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return type.equals(PATCHStatusContext.class);
+ }
+
+ @Override
+ public long getSize(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
+ annotations, MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
+ annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+ throws IOException, WebApplicationException {
+
+ final JsonWriter jsonWriter = createJsonWriter(entityStream);
+ jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status");
+ jsonWriter.beginObject();
+ jsonWriter.name("patch-id").value(patchStatusContext.getPatchId());
+ if (patchStatusContext.isOk()) {
+ jsonWriter.name("ok").nullValue();
+ } else {
+ if (patchStatusContext.getGlobalErrors() != null) {
+ reportErrors(patchStatusContext.getGlobalErrors(), jsonWriter);
+ }
+
+ jsonWriter.name("edit-status");
+ jsonWriter.beginObject();
+ jsonWriter.name("edit");
+ jsonWriter.beginArray();
+ for (PATCHStatusEntity patchStatusEntity : patchStatusContext.getEditCollection()) {
+ jsonWriter.beginObject();
+ jsonWriter.name("edit-id").value(patchStatusEntity.getEditId());
+ if (patchStatusEntity.getEditErrors() != null) {
+ reportErrors(patchStatusEntity.getEditErrors(), jsonWriter);
+ } else {
+ if (patchStatusEntity.isOk()) {
+ jsonWriter.name("ok").nullValue();
+ }
+ }
+ jsonWriter.endObject();
+ }
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ }
+ jsonWriter.endObject();
+ jsonWriter.endObject();
+ jsonWriter.flush();
+
+ }
+
+ private static void reportErrors(List<RestconfError> errors, JsonWriter jsonWriter) throws IOException {
+ jsonWriter.name("errors");
+ jsonWriter.beginObject();
+ jsonWriter.name("error");
+ jsonWriter.beginArray();
+
+ for (RestconfError restconfError : errors) {
+ jsonWriter.beginObject();
+ jsonWriter.name("error-type").value(restconfError.getErrorType().getErrorTypeTag());
+ jsonWriter.name("error-tag").value(restconfError.getErrorTag().getTagValue());
+ //TODO: fix error-path reporting (separate error-path from error-message)
+ //jsonWriter.name("error-path").value(restconfError.getErrorPath());
+ jsonWriter.name("error-message").value(restconfError.getErrorMessage());
+ jsonWriter.endObject();
+ }
+
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ }
+
+ private static JsonWriter createJsonWriter(final OutputStream entityStream) {
+ return JsonWriterFactory.createJsonWriter(new OutputStreamWriter(entityStream, Charsets.UTF_8));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 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.sal.rest.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+
+@Provider
+@Produces({ MediaTypes.PATCH_STATUS + RestconfService.XML})
+public class PATCHXmlBodyWriter implements MessageBodyWriter<PATCHStatusContext> {
+
+ private static final XMLOutputFactory XML_FACTORY;
+
+ static {
+ XML_FACTORY = XMLOutputFactory.newFactory();
+ XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return type.equals(PATCHStatusContext.class);
+ }
+
+ @Override
+ public long getSize(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
+ annotations, MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(PATCHStatusContext patchStatusContext, Class<?> type, Type genericType, Annotation[]
+ annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+ throws IOException, WebApplicationException {
+
+ try {
+ XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream);
+ writeDocument(xmlWriter, patchStatusContext);
+ } catch (final XMLStreamException e) {
+ throw new IllegalStateException(e);
+ } catch (final FactoryConfigurationError e) {
+ throw new IllegalStateException(e);
+ }
+
+ }
+
+ private static void writeDocument(XMLStreamWriter writer, PATCHStatusContext context) throws XMLStreamException, IOException {
+ writer.writeStartElement("", "yang-patch-status", "urn:ietf:params:xml:ns:yang:ietf-yang-patch");
+ writer.writeStartElement("patch-id");
+ writer.writeCharacters(context.getPatchId());
+ writer.writeEndElement();
+
+ if (context.isOk()) {
+ writer.writeEmptyElement("ok");
+ } else {
+ if (context.getGlobalErrors() != null) {
+ reportErrors(context.getGlobalErrors(), writer);
+ }
+ writer.writeStartElement("edit-status");
+ for (PATCHStatusEntity patchStatusEntity : context.getEditCollection()) {
+ writer.writeStartElement("edit");
+ writer.writeStartElement("edit-id");
+ writer.writeCharacters(patchStatusEntity.getEditId());
+ writer.writeEndElement();
+ if (patchStatusEntity.getEditErrors() != null) {
+ reportErrors(patchStatusEntity.getEditErrors(), writer);
+ } else {
+ if (patchStatusEntity.isOk()) {
+ writer.writeEmptyElement("ok");
+ }
+ }
+ writer.writeEndElement();
+ }
+ writer.writeEndElement();
+
+ }
+ writer.writeEndElement();
+
+ writer.flush();
+ }
+
+ private static void reportErrors(List<RestconfError> errors, XMLStreamWriter writer) throws IOException, XMLStreamException {
+ writer.writeStartElement("errors");
+
+ for (RestconfError restconfError : errors) {
+ writer.writeStartElement("error-type");
+ writer.writeCharacters(restconfError.getErrorType().getErrorTypeTag());
+ writer.writeEndElement();
+ writer.writeStartElement("error-tag");
+ writer.writeCharacters(restconfError.getErrorTag().getTagValue());
+ writer.writeEndElement();
+ //TODO: fix error-path reporting (separate error-path from error-message)
+// writer.writeStartElement("error-path");
+// writer.writeCharacters(restconfError.getErrorPath());
+// writer.writeEndElement();
+ writer.writeStartElement("error-message");
+ writer.writeCharacters(restconfError.getErrorMessage());
+ writer.writeEndElement();
+ }
+
+ writer.writeEndElement();
+ }
+}
.add(RestconfDocumentedExceptionMapper.class)
.add(XmlNormalizedNodeBodyReader.class)
.add(JsonNormalizedNodeBodyReader.class)
+ .add(JsonToPATCHBodyReader.class)
+ .add(XmlToPATCHBodyReader.class)
+ .add(PATCHJsonBodyWriter.class)
+ .add(PATCHXmlBodyWriter.class)
.add(NormalizedNodeJsonBodyWriter.class)
.add(NormalizedNodeXmlBodyWriter.class)
.add(SchemaExportContentYinBodyWriter.class)
package org.opendaylight.netconf.sal.rest.impl;
import com.google.common.base.Preconditions;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
import org.opendaylight.netconf.md.sal.rest.schema.SchemaRetrievalService;
import org.opendaylight.netconf.sal.rest.api.RestconfService;
import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
public class RestconfCompositeWrapper implements RestconfService, SchemaRetrievalService {
return restconf.getAvailableStreams(uriInfo);
}
+ @Override
+ public PATCHStatusContext patchConfigurationData(final String identifier, PATCHContext payload, UriInfo uriInfo) {
+ return restconf.patchConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public PATCHStatusContext patchConfigurationData(final PATCHContext context, final UriInfo uriInfo) {
+ return restconf.patchConfigurationData(context, uriInfo);
+ }
+
@Override
public SchemaExportContext getSchema(final String mountId) {
return schema.getSchema(mountId);
--- /dev/null
+/*
+ * Copyright (c) 2015 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.sal.rest.impl;
+
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes;
+import org.opendaylight.netconf.sal.rest.api.RestconfService;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+@Provider
+@Consumes({MediaTypes.PATCH + RestconfService.XML})
+public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements
+ MessageBodyReader<PATCHContext> {
+
+ private final static Logger LOG = LoggerFactory.getLogger(XmlToPATCHBodyReader.class);
+ private static final DocumentBuilderFactory BUILDERFACTORY;
+
+ static {
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ try {
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ factory.setXIncludeAware(false);
+ factory.setExpandEntityReferences(false);
+ } catch (final ParserConfigurationException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ factory.setNamespaceAware(true);
+ factory.setCoalescing(true);
+ factory.setIgnoringElementContentWhitespace(true);
+ factory.setIgnoringComments(true);
+ BUILDERFACTORY = factory;
+ }
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return true;
+ }
+
+ @Override
+ public PATCHContext readFrom(Class<PATCHContext> type, Type genericType, Annotation[] annotations, MediaType
+ mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException,
+ WebApplicationException {
+
+ try {
+ final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
+
+ if (entityStream.available() < 1) {
+ // represent empty nopayload input
+ return new PATCHContext(path, null, null);
+ }
+
+ final DocumentBuilder dBuilder;
+ try {
+ dBuilder = BUILDERFACTORY.newDocumentBuilder();
+ } catch (final ParserConfigurationException e) {
+ throw new IllegalStateException("Failed to parse XML document", e);
+ }
+ final Document doc = dBuilder.parse(entityStream);
+
+ return parse(path, doc);
+ } catch (final RestconfDocumentedException e) {
+ throw e;
+ } catch (final Exception e) {
+ LOG.debug("Error parsing xml input", e);
+
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE);
+ }
+ }
+
+ private PATCHContext parse(final InstanceIdentifierContext<?> pathContext, final Document doc) {
+ final List<PATCHEntity> resultCollection = new ArrayList<>();
+ final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
+ final NodeList editNodes = doc.getElementsByTagName("edit");
+ final DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode();
+ final DomToNormalizedNodeParserFactory parserFactory =
+ DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER,
+ pathContext.getSchemaContext());
+
+ for (int i = 0; i < editNodes.getLength(); i++) {
+ Element element = (Element) editNodes.item(i);
+ final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue();
+ final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue();
+ final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue();
+ DataSchemaNode targetNode = ((DataNodeContainer)(pathContext.getSchemaNode())).getDataChildByName
+ (target.replace("/", ""));
+ if (targetNode == null) {
+ LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode());
+ throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE);
+ } else {
+ final YangInstanceIdentifier targetII = pathContext.getInstanceIdentifier().node(targetNode.getQName());
+ final NodeList valueNodes = element.getElementsByTagName("value").item(0).getChildNodes();
+ Element value = null;
+ for (int j = 0; j < valueNodes.getLength(); j++) {
+ if (valueNodes.item(j) instanceof Element) {
+ value = (Element) valueNodes.item(j);
+ break;
+ }
+ }
+ NormalizedNode<?, ?> parsed = null;
+ if (schemaNode instanceof ContainerSchemaNode) {
+ parsed = parserFactory.getContainerNodeParser().parse(Collections.singletonList(value),
+ (ContainerSchemaNode) targetNode);
+ } else if (schemaNode instanceof ListSchemaNode) {
+ NormalizedNode<?, ?> parsedValue = parserFactory.getMapEntryNodeParser().parse(Collections
+ .singletonList(value), (ListSchemaNode) targetNode);
+ parsed = ImmutableNodes.mapNodeBuilder().withNodeIdentifier(new NodeIdentifier
+ (targetNode.getQName())).withChild((MapEntryNode) parsedValue).build();
+ }
+
+ resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed));
+ }
+ }
+
+ return new PATCHContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
+ }
+}
import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
throw new RestconfDocumentedException(errMsg);
}
+ public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
+ final SchemaContext globalSchema) {
+ final DOMDataReadWriteTransaction patchTransaction = domDataBroker.newReadWriteTransaction();
+ List<PATCHStatusEntity> editCollection = new ArrayList<>();
+ List<RestconfError> editErrors;
+ List<RestconfError> globalErrors = null;
+ int errorCounter = 0;
+
+ for (PATCHEntity patchEntity : context.getData()) {
+ final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
+
+ switch (operation) {
+ case CREATE:
+ if (errorCounter == 0) {
+ try {
+ postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+ patchEntity.getNode(), globalSchema);
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ errorCounter++;
+ }
+ }
+ break;
+ case REPLACE:
+ if (errorCounter == 0) {
+ try {
+ putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode(), patchEntity.getNode(), globalSchema);
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ errorCounter++;
+ }
+ }
+ break;
+ case DELETE:
+ if (errorCounter == 0) {
+ try {
+ deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ editErrors = new ArrayList<>();
+ editErrors.addAll(e.getErrors());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+ errorCounter++;
+ }
+ }
+ break;
+ case REMOVE:
+ if (errorCounter == 0) {
+ try {
+ deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+ .getTargetNode());
+ editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+ } catch (RestconfDocumentedException e) {
+ LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
+ patchEntity.getEditId(), e);
+ }
+ }
+ break;
+ }
+ }
+
+ //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
+ //globalErrors = new ArrayList<>();
+ if (errorCounter == 0) {
+ final CheckedFuture<Void, TransactionCommitFailedException> submit = patchTransaction.submit();
+ return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true,
+ globalErrors);
+ } else {
+ patchTransaction.cancel();
+ return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
+ globalErrors);
+ }
+ }
+
// POST configuration
public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
- LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
+ LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
if (listenableFuture != null) {
Optional<NormalizedNode<?, ?>> optional;
LOG.debug("Reading result data from transaction.");
optional = listenableFuture.get();
} catch (InterruptedException | ExecutionException e) {
- LOG.warn("Exception by reading " + datastore.name() + " via Restconf: {}", path, e);
+ LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
}
// FIXME: This is doing correct post for container and list children
// not sure if this will work for choice case
if(payload instanceof MapNode) {
- LOG.trace("POST " + datastore.name() + " via Restconf: {} with payload {}", path, payload);
+ LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
return rWTransaction.submit();
}
+ private void postDataWithinTransaction(
+ final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+ // FIXME: This is doing correct post for container and list children
+ // not sure if this will work for choice case
+ if(payload instanceof MapNode) {
+ LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+ final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+ rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+ ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
+ for(final MapEntryNode child : ((MapNode) payload).getValue()) {
+ final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+ checkItemDoesNotExists(rWTransaction, datastore, childPath);
+ rWTransaction.put(datastore, childPath, child);
+ }
+ } else {
+ checkItemDoesNotExists(rWTransaction,datastore, path);
+ ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
+ rWTransaction.put(datastore, path, payload);
+ }
+ }
+
private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
try {
if (futureDatastoreData.get()) {
final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
- LOG.trace(errMsg + ":{}", path);
+ LOG.trace("{}:{}", errMsg, path);
rWTransaction.cancel();
throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
ErrorTag.DATA_EXISTS);
}
} catch (InterruptedException | ExecutionException e) {
- LOG.warn("It wasn't possible to get data loaded from datastore at path " + path, e);
+ LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
}
}
private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
- LOG.trace("Put " + datastore.name() + " via Restconf: {} with payload {}", path, payload);
+ LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
writeTransaction.put(datastore, path, payload);
return writeTransaction.submit();
}
+ private void putDataWithinTransaction(
+ final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+ LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+ ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+ writeTransaction.put(datastore, path, payload);
+ }
+
private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
final YangInstanceIdentifier path) {
- LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
+ LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
writeTransaction.delete(datastore, path);
return writeTransaction.submit();
}
+ private void deleteDataWithinTransaction(
+ final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+ final YangInstanceIdentifier path) {
+ LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
+ writeTransaction.delete(datastore, path);
+ }
+
public void setDomDataBroker(final DOMDataBroker domDataBroker) {
this.domDataBroker = domDataBroker;
}
--- /dev/null
+/*
+ * Copyright (c) 2015 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.sal.restconf.impl;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+public class PATCHContext {
+
+ private final InstanceIdentifierContext<? extends SchemaNode> context;
+ private final List<PATCHEntity> data;
+ private final String patchId;
+
+ public PATCHContext(final InstanceIdentifierContext<? extends SchemaNode> context,
+ final List<PATCHEntity> data, final String patchId) {
+ this.context = Preconditions.checkNotNull(context);
+ this.data = Preconditions.checkNotNull(data);
+ this.patchId = Preconditions.checkNotNull(patchId);
+ }
+
+ public InstanceIdentifierContext<? extends SchemaNode> getInstanceIdentifierContext() {
+ return context;
+ }
+
+ public List<PATCHEntity> getData() {
+ return data;
+ }
+
+ public String getPatchId() {
+ return patchId;
+ }
+}
--- /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.sal.restconf.impl;
+
+/**
+ *
+ * Each YANG patch edit specifies one edit operation on the target data
+ * node. The set of operations is aligned with the NETCONF edit
+ * operations, but also includes some new operations.
+ *
+ */
+enum PATCHEditOperation {
+ CREATE, //post
+ DELETE, //delete
+ INSERT, //post
+ MERGE,
+ MOVE, //delete+post
+ REPLACE, //put
+ REMOVE //delete
+}
--- /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.sal.restconf.impl;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class PATCHEntity {
+
+ private final String operation;
+ private final String editId;
+ private final YangInstanceIdentifier targetNode;
+ private final NormalizedNode<?,?> node;
+
+ public PATCHEntity(final String editId, final String operation, final YangInstanceIdentifier targetNode, final
+ NormalizedNode<?, ?> node) {
+ this.editId = Preconditions.checkNotNull(editId);
+ this.operation = Preconditions.checkNotNull(operation);
+ this.targetNode = Preconditions.checkNotNull(targetNode);
+ this.node = Preconditions.checkNotNull(node);
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+
+ public String getEditId() {
+ return editId;
+ }
+
+ public YangInstanceIdentifier getTargetNode() {
+ return targetNode;
+ }
+
+ public NormalizedNode<?, ?> getNode() {
+ return node;
+ }
+
+}
--- /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.sal.restconf.impl;
+
+import java.util.List;
+
+public class PATCHStatusContext {
+
+ private final String patchId;
+ private final List<PATCHStatusEntity> editCollection;
+ private boolean ok;
+ private List<RestconfError> globalErrors;
+
+ public PATCHStatusContext(final String patchId, final List<PATCHStatusEntity> editCollection,
+ final boolean ok, final List<RestconfError> globalErrors) {
+ this.patchId = patchId;
+ this.editCollection = editCollection;
+ this.ok = ok;
+ this.globalErrors = globalErrors;
+ }
+
+ public String getPatchId() {
+ return patchId;
+ }
+
+ public List<PATCHStatusEntity> getEditCollection() {
+ return editCollection;
+ }
+
+ public boolean isOk() {
+ return ok;
+ }
+
+ public List<RestconfError> getGlobalErrors() {
+ return globalErrors;
+ }
+}
--- /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.sal.restconf.impl;
+
+import java.util.List;
+
+public class PATCHStatusEntity {
+
+ private final String editId;
+ private final List<RestconfError> editErrors;
+ private final boolean ok;
+
+ public PATCHStatusEntity(final String editId, final boolean ok, final List<RestconfError> editErrors) {
+ this.editId = editId;
+ this.ok = ok;
+ this.editErrors = editErrors;
+ }
+
+ public String getEditId() {
+ return editId;
+ }
+
+ public boolean isOk() {
+ return ok;
+ }
+
+ public List<RestconfError> getEditErrors() {
+ return editErrors;
+ }
+}
-/**
+/*
* Copyright (c) 2014, 2015 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
return Response.status(Status.OK).location(uriToWebsocketServer).build();
}
+ @Override
+ public PATCHStatusContext patchConfigurationData(String identifier, PATCHContext context, UriInfo uriInfo) {
+ if (context == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema());
+ }
+
+ @Override
+ public PATCHStatusContext patchConfigurationData(PATCHContext context, @Context UriInfo uriInfo) {
+ if (context == null) {
+ throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+ return broker.patchConfigurationDataWithinTransaction(context, controllerContext.getGlobalSchema());
+ }
+
/**
* Load parameter for subscribing to stream from input composite node
*
import java.math.BigInteger;
import java.util.concurrent.atomic.AtomicLong;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
return delegate.getAvailableStreams(uriInfo);
}
+ @Override
+ public PATCHStatusContext patchConfigurationData(final String identifier, final PATCHContext payload, final UriInfo
+ uriInfo) {
+ return delegate.patchConfigurationData(identifier, payload, uriInfo);
+ }
+
+ @Override
+ public PATCHStatusContext patchConfigurationData(final PATCHContext payload, final UriInfo uriInfo) {
+ return delegate.patchConfigurationData(payload, uriInfo);
+ }
+
public BigInteger getConfigDelete() {
return BigInteger.valueOf(configDelete.get());
}
--- /dev/null
+module instance-identifier-patch-module {
+ namespace "instance:identifier:patch:module";
+
+ prefix "iipmodule";
+ revision 2015-11-21 {
+ }
+
+ container patch-cont {
+ container patch-cont2 {
+ leaf cont-leaf {
+ type string;
+ }
+ }
+
+ list my-list1 {
+
+ description "PATCH /restconf/config/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf my-leaf11 {
+ type string;
+ }
+
+ leaf my-leaf12 {
+ type string;
+ }
+
+ list my-list2 {
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf my-leaf21 {
+ type string;
+ }
+
+ leaf my-leaf22 {
+ type string;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
-/**
+/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
-/**
+/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
import org.opendaylight.netconf.sal.rest.impl.AbstractIdentifierAwareJaxRsProvider;
import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
/**
.getSchemaContext());
assertNotNull(nnContext.getInstanceIdentifierContext().getSchemaNode());
}
+
+ protected static void checkPATCHContext(final PATCHContext patchContext) {
+ assertNotNull(patchContext.getData());
+ assertNotNull(patchContext.getInstanceIdentifierContext().getInstanceIdentifier());
+ assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaContext());
+ assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaNode());
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2015 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.controller.sal.rest.impl.test.providers;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.rest.impl.JsonToPATCHBodyReader;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestJsonPATCHBodyReader extends AbstractBodyReaderTest {
+
+ private final JsonToPATCHBodyReader jsonPATCHBodyReader;
+ private static SchemaContext schemaContext;
+
+ public TestJsonPATCHBodyReader() throws NoSuchFieldException, SecurityException {
+ super();
+ jsonPATCHBodyReader = new JsonToPATCHBodyReader();
+ }
+
+ @Override
+ protected MediaType getMediaType() {
+ return new MediaType(APPLICATION_JSON, null);
+ }
+
+ @BeforeClass
+ public static void initialization() {
+ schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
+ controllerContext.setSchemas(schemaContext);
+ }
+
+ @Test
+ public void modulePATCHDataTest() throws Exception {
+ final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+ final InputStream inputStream = TestJsonBodyReader.class
+ .getResourceAsStream("/instanceidentifier/json/jsonPATCHdata.json");
+
+ final PATCHContext returnValue = jsonPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContext(returnValue);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 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.controller.sal.rest.impl.test.providers;
+
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.rest.impl.XmlToPATCHBodyReader;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestXmlPATCHBodyReader extends AbstractBodyReaderTest {
+
+ private final XmlToPATCHBodyReader xmlPATCHBodyReader;
+ private static SchemaContext schemaContext;
+
+ public TestXmlPATCHBodyReader() throws NoSuchFieldException, SecurityException {
+ super();
+ xmlPATCHBodyReader = new XmlToPATCHBodyReader();
+ }
+
+ @Override
+ protected MediaType getMediaType() {
+ return new MediaType(MediaType.APPLICATION_XML, null);
+ }
+
+ @BeforeClass
+ public static void initialization() throws NoSuchFieldException, SecurityException {
+ schemaContext = schemaContextLoader("/instanceidentifier/yang", schemaContext);
+ controllerContext.setSchemas(schemaContext);
+ }
+
+ @Test
+ public void moduleDataTest() throws Exception {
+ final String uri = "instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+ mockBodyReader(uri, xmlPATCHBodyReader, false);
+ final InputStream inputStream = TestXmlBodyReader.class
+ .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdata.xml");
+ final PATCHContext returnValue = xmlPATCHBodyReader
+ .readFrom(null, null, null, mediaType, null, inputStream);
+ checkPATCHContext(returnValue);
+ }
+}
--- /dev/null
+{
+ "ietf-yang-patch:yang-patch" : {
+
+ "patch-id" : "test-patch",
+ "comment" : "this is test patch",
+ "edit" : [
+ {
+ "edit-id": "edit1",
+ "operation": "create",
+ "target": "/my-list2",
+ "value": {
+ "my-list2": {
+ "name": "my-leaf20",
+ "my-leaf21": "I am leaf21-0",
+ "my-leaf22": "I am leaf22-0"
+ }
+ }
+ },
+
+ {
+ "edit-id": "edit2",
+ "operation": "create",
+ "target": "/my-list2",
+ "value": {
+ "my-list2": {
+ "name": "my-leaf21",
+ "my-leaf21": "I am leaf21-1",
+ "my-leaf22": "I am leaf22-1"
+ }
+ }
+ }
+ ]
+ }
+}
--- /dev/null
+<yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>test-patch</patch-id>
+ <comment>this is test patch</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>create</operation>
+ <target>/my-list2</target>
+ <value>
+ <my-list2 xmlns="instance:identifier:patch:module">
+ <name>my-leaf20</name>
+ <my-leaf21>I am leaf21-0</my-leaf21>
+ <my-leaf22>I am leaf22-0</my-leaf22>
+ </my-list2>
+ </value>
+ </edit>
+ <edit>
+ <edit-id>edit2</edit-id>
+ <operation>create</operation>
+ <target>/my-list2</target>
+ <value>
+ <my-list2 xmlns="instance:identifier:patch:module">
+ <name>my-leaf21</name>
+ <my-leaf21>I am leaf21-1</my-leaf21>
+ <my-leaf22>I am leaf22-1</my-leaf22>
+ </my-list2>
+ </value>
+ </edit>
+</yang-patch>
\ No newline at end of file
--- /dev/null
+module instance-identifier-patch-module {
+ namespace "instance:identifier:patch:module";
+
+ prefix "iipmodule";
+ revision 2015-11-21 {
+ }
+
+ container patch-cont {
+ list my-list1 {
+
+ description "PATCH /restconf/config/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf my-leaf11 {
+ type string;
+ }
+
+ leaf my-leaf12 {
+ type string;
+ }
+
+ list my-list2 {
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf my-leaf21 {
+ type string;
+ }
+
+ leaf my-leaf22 {
+ type string;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file