<bundle>mvn:org.opendaylight.netconf/ietf-netconf/{{VERSION}}</bundle>
<bundle>mvn:org.opendaylight.netconf/ietf-netconf-notifications/{{VERSION}}</bundle>
<bundle>mvn:org.opendaylight.netconf/ietf-netconf-monitoring-extension/{{VERSION}}</bundle>
- <bundle>mvn:org.opendaylight.mdsal.model/ietf-inet-types/{{VERSION}}</bundle>
- <bundle>mvn:org.opendaylight.mdsal.model/ietf-yang-types/{{VERSION}}</bundle>
- <bundle>mvn:org.opendaylight.mdsal.model/ietf-yang-types-20130715/2013.07.15.9-SNAPSHOT</bundle>
- <bundle>mvn:org.opendaylight.mdsal.model/ietf-type-util/{{VERSION}}</bundle>
+ <feature version='${mdsal.model.version}'>odl-mdsal-models</feature>
</feature>
<feature name='odl-netconf-mapping-api' version='${project.version}' description="OpenDaylight :: Netconf :: Mapping API">
<groupId>org.opendaylight.netconf</groupId>
<artifactId>ietf-netconf</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-broker-impl</artifactId>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal-distributed-datastore</artifactId>
return document;
}
- //TODO move all occurences of this method in mdsal netconf(and xml factories) to a utility class
private Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data, final SchemaPath rpcOutputPath) {
final DOMResult result = new DOMResult(document.createElement(XmlMappingConstants.RPC_REPLY_KEY));
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;
return SchemaPath.create(Iterables.transform(dataRoot.getPathArguments(), PATH_ARG_TO_QNAME), dataRoot.equals(ROOT));
}
- // TODO this code is located in Restconf already
private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
try {
if (data.getNodeType().equals(SchemaContext.NAME)) {
}
}
- 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.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import org.opendaylight.controller.cluster.datastore.ConcurrentDOMDataBroker;
+import org.opendaylight.controller.cluster.databroker.ConcurrentDOMDataBroker;
import org.opendaylight.controller.config.util.xml.DocumentedException;
import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
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);
+ }
+ }
+ }
}
}
}
private AnyXmlNode encapsulate(final DOMNotification body) {
- // FIXME: Introduce something like AnyXmlWithNormalizedNodeData in Yangtools
+ // FIXME: Introduce something like YangModeledAnyXmlNode in Yangtools
final Document doc = XmlUtil.newDocument();
final Optional<String> namespace = Optional.of(PAYLOAD_ARG.getNodeType().getNamespace().toString());
final Element element = XmlUtil.createElement(doc, "payload", namespace);
package org.opendaylight.netconf.api.messages;
import com.google.common.base.Preconditions;
+import com.google.common.net.InetAddresses;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
return sb.toString();
}
- // TODO IPv6
private static final Pattern PATTERN = Pattern
- .compile("\\[(?<username>[^;]+);(?<address>[0-9\\.]+)[:/](?<port>[0-9]+);(?<transport>[a-z]+)[^\\]]+\\]");
+ .compile("\\[(?<username>[^;]+);(?<address>.+)[:/](?<port>[0-9]+);(?<transport>[a-z]+)[^\\]]+\\]");
private static final Pattern CUSTOM_HEADER_PATTERN = Pattern
- .compile("\\[(?<username>[^;]+);(?<address>[0-9\\.]+)[:/](?<port>[0-9]+);(?<transport>[a-z]+);(?<sessionIdentifier>[a-z]+)[^\\]]+\\]");
+ .compile("\\[(?<username>[^;]+);(?<address>.+)[:/](?<port>[0-9]+);(?<transport>[a-z]+);(?<sessionIdentifier>[a-z]+)[^\\]]+\\]");
/**
* Parse additional header from a formatted string
String username = matcher.group("username");
String address = matcher.group("address");
+ Preconditions.checkArgument(InetAddresses.isInetAddress(address));
String port = matcher.group("port");
String transport = matcher.group("transport");
String sessionIdentifier = "client";
/*
- * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ * 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,
package org.opendaylight.controller.config.yang.config.netconf.northbound.impl;
import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+import org.opendaylight.netconf.impl.NetconfServerSessionNegotiatorFactoryBuilder;
import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.netconf.impl.NetconfServerDispatcherImpl;
import org.opendaylight.netconf.impl.SessionIdProvider;
import org.opendaylight.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory;
-public class NetconfServerDispatcherModule extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfServerDispatcherModule {
+public class NetconfServerDispatcherModule extends AbstractNetconfServerDispatcherModule {
public NetconfServerDispatcherModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
super(identifier, dependencyResolver);
}
- public NetconfServerDispatcherModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.config.netconf.northbound.impl.NetconfServerDispatcherModule oldModule, java.lang.AutoCloseable oldInstance) {
+ public NetconfServerDispatcherModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, NetconfServerDispatcherModule oldModule, AutoCloseable oldInstance) {
super(identifier, dependencyResolver, oldModule, oldInstance);
}
}
@Override
- public java.lang.AutoCloseable createInstance() {
+ public AutoCloseable createInstance() {
final AggregatedNetconfOperationServiceFactory aggregatedOpProvider = getAggregatedOpProvider();
final NetconfMonitoringService monitoringService = getServerMonitorDependency();
- final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory(
- getTimerDependency(), aggregatedOpProvider, new SessionIdProvider(), getConnectionTimeoutMillis(), monitoringService);
+
+ final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactoryBuilder()
+ .setAggregatedOpService(aggregatedOpProvider)
+ .setTimer(getTimerDependency())
+ .setIdProvider(new SessionIdProvider())
+ .setMonitoringService(monitoringService)
+ .setConnectionTimeoutMillis(getConnectionTimeoutMillis())
+ .build();
final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(
serverNegotiatorFactory);
package org.opendaylight.netconf.impl;
import com.google.common.base.Preconditions;
+import com.google.common.net.InetAddresses;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
+import java.net.Inet4Address;
+import java.net.InetAddress;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.opendaylight.netconf.nettyutil.AbstractNetconfSession;
import org.opendaylight.netconf.nettyutil.handler.NetconfMessageToXMLEncoder;
import org.opendaylight.netconf.nettyutil.handler.NetconfXMLToMessageDecoder;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.DomainName;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Host;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv6Address;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.NetconfTcp;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.Session1;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.Session1Builder;
SessionBuilder builder = new SessionBuilder();
builder.setSessionId(getSessionId());
- builder.setSourceHost(new Host(new DomainName(header.getAddress())));
+ IpAddress address;
+ InetAddress address1 = InetAddresses.forString(header.getAddress());
+ if (address1 instanceof Inet4Address) {
+ address = new IpAddress(new Ipv4Address(header.getAddress()));
+ } else {
+ address = new IpAddress(new Ipv6Address(header.getAddress()));
+ }
+ builder.setSourceHost(new Host(address));
Preconditions.checkState(DateAndTime.PATTERN_CONSTANTS.size() == 1);
String formattedDateTime = dateFormatter.format(loginTime);
try {
Preconditions.checkState(operationRouter != null, "Cannot handle message, session up was not yet received");
- // FIXME: there is no validation since the document may contain yang
- // schemas
+ // there is no validation since the document may contain yang schemas
final NetconfMessage message = processDocument(netconfMessage,
session);
LOG.debug("Responding with message {}", message);
import io.netty.util.concurrent.Promise;
import java.net.SocketAddress;
import java.util.Set;
-import org.opendaylight.netconf.mapping.api.NetconfOperationService;
-import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
-import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
import org.opendaylight.netconf.api.NetconfDocumentedException;
import org.opendaylight.netconf.api.NetconfServerSessionPreferences;
+import org.opendaylight.netconf.api.messages.NetconfHelloMessage;
import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
import org.opendaylight.netconf.impl.osgi.NetconfOperationRouter;
import org.opendaylight.netconf.impl.osgi.NetconfOperationRouterImpl;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
import org.opendaylight.protocol.framework.SessionListenerFactory;
import org.opendaylight.protocol.framework.SessionNegotiator;
import org.opendaylight.protocol.framework.SessionNegotiatorFactory;
private static final Logger LOG = LoggerFactory.getLogger(NetconfServerSessionNegotiatorFactory.class);
private final Set<String> baseCapabilities;
- // TODO too many params, refactor
- public NetconfServerSessionNegotiatorFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider,
- final SessionIdProvider idProvider, final long connectionTimeoutMillis,
- final NetconfMonitoringService monitoringService) {
- this(timer, netconfOperationProvider, idProvider, connectionTimeoutMillis, monitoringService, DEFAULT_BASE_CAPABILITIES);
- }
-
- // TODO too many params, refactor
- public NetconfServerSessionNegotiatorFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider,
+ protected NetconfServerSessionNegotiatorFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider,
final SessionIdProvider idProvider, final long connectionTimeoutMillis,
final NetconfMonitoringService monitoringService, final Set<String> baseCapabilities) {
this.timer = timer;
this.idProvider = idProvider;
this.connectionTimeoutMillis = connectionTimeoutMillis;
this.monitoringService = monitoringService;
- this.baseCapabilities = validateBaseCapabilities(baseCapabilities);
+ this.baseCapabilities = validateBaseCapabilities(baseCapabilities == null ? DEFAULT_BASE_CAPABILITIES : baseCapabilities);
}
+
private static ImmutableSet<String> validateBaseCapabilities(final Set<String> baseCapabilities) {
// Check base capabilities to be supported by the server
final Sets.SetView<String> unknownBaseCaps = Sets.difference(baseCapabilities, DEFAULT_BASE_CAPABILITIES);
}
/**
- *
* @param defunctSessionListenerFactory will not be taken into account as session listener factory can
* only be created after snapshot is opened, thus this method constructs
* proper session listener factory.
--- /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.impl;
+
+import com.google.common.base.Preconditions;
+import io.netty.util.Timer;
+import java.util.Set;
+import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+
+public class NetconfServerSessionNegotiatorFactoryBuilder {
+ private Timer timer;
+ private SessionIdProvider idProvider;
+ private NetconfOperationServiceFactory aggregatedOpService;
+ private long connectionTimeoutMillis;
+ private NetconfMonitoringService monitoringService;
+ private Set<String> baseCapabilities;
+
+ public NetconfServerSessionNegotiatorFactoryBuilder() {
+ }
+
+ public NetconfServerSessionNegotiatorFactoryBuilder setTimer(final Timer timer) {
+ this.timer = timer;
+ return this;
+ }
+
+ public NetconfServerSessionNegotiatorFactoryBuilder setIdProvider(final SessionIdProvider idProvider) {
+ this.idProvider = idProvider;
+ return this;
+ }
+
+ public NetconfServerSessionNegotiatorFactoryBuilder setAggregatedOpService(final NetconfOperationServiceFactory aggregatedOpService) {
+ this.aggregatedOpService = aggregatedOpService;
+ return this;
+ }
+
+ public NetconfServerSessionNegotiatorFactoryBuilder setConnectionTimeoutMillis(final long connectionTimeoutMillis) {
+ this.connectionTimeoutMillis = connectionTimeoutMillis;
+ return this;
+ }
+
+ public NetconfServerSessionNegotiatorFactoryBuilder setMonitoringService(final NetconfMonitoringService monitoringService) {
+ this.monitoringService = monitoringService;
+ return this;
+ }
+
+ public NetconfServerSessionNegotiatorFactoryBuilder setBaseCapabilities(final Set<String> baseCapabilities) {
+ this.baseCapabilities = baseCapabilities;
+ return this;
+ }
+
+
+ public NetconfServerSessionNegotiatorFactory build() {
+ validate();
+ return new NetconfServerSessionNegotiatorFactory(timer, aggregatedOpService, idProvider, connectionTimeoutMillis, monitoringService, baseCapabilities);
+ }
+
+
+ private void validate() {
+ Preconditions.checkNotNull(timer, "timer not initialized");
+ Preconditions.checkNotNull(aggregatedOpService, "NetconfOperationServiceFactory not initialized");
+ Preconditions.checkNotNull(idProvider, "SessionIdProvider not initialized");
+ Preconditions.checkArgument(connectionTimeoutMillis > 0, "connection time out <=0");
+ Preconditions.checkNotNull(monitoringService, "NetconfMonitoringService not initialized");
+
+ baseCapabilities = (baseCapabilities == null) ? NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES : baseCapabilities;
+ }
+}
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.concurrent.TimeUnit;
-import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactoryListener;
-import org.opendaylight.netconf.notifications.BaseNotificationPublisherRegistration;
-import org.opendaylight.netconf.notifications.NetconfNotificationCollector;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.netconf.impl.NetconfServerDispatcherImpl;
import org.opendaylight.netconf.impl.NetconfServerDispatcherImpl.ServerChannelInitializer;
import org.opendaylight.netconf.impl.NetconfServerSessionNegotiatorFactory;
+import org.opendaylight.netconf.impl.NetconfServerSessionNegotiatorFactoryBuilder;
import org.opendaylight.netconf.impl.SessionIdProvider;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactoryListener;
+import org.opendaylight.netconf.notifications.BaseNotificationPublisherRegistration;
+import org.opendaylight.netconf.notifications.NetconfNotificationCollector;
+import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
private BaseNotificationPublisherRegistration listenerReg;
@Override
- public void start(final BundleContext context) {
+ public void start(final BundleContext context) {
try {
AggregatedNetconfOperationServiceFactory factoriesListener = new AggregatedNetconfOperationServiceFactory();
startOperationServiceFactoryTracker(context, factoriesListener);
final NetconfMonitoringServiceImpl monitoringService = startMonitoringService(context, factoriesListener);
- NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory(
- timer, factoriesListener, idProvider, connectionTimeoutMillis, monitoringService);
+ NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactoryBuilder()
+ .setAggregatedOpService(factoriesListener)
+ .setTimer(timer)
+ .setIdProvider(idProvider)
+ .setMonitoringService(monitoringService)
+ .setConnectionTimeoutMillis(connectionTimeoutMillis)
+ .build();
eventLoopGroup = new NioEventLoopGroup();
private synchronized void onCapabilitiesAdded(final Set<Capability> addedCaps) {
- // FIXME howto check for duplicates
this.capabilities.putAll(Maps.uniqueIndex(setupCapabilities(addedCaps), CAPABILITY_TO_URI));
}
import org.opendaylight.controller.config.util.capability.Capability;
import org.opendaylight.controller.config.util.xml.DocumentedException;
import org.opendaylight.controller.config.util.xml.XmlUtil;
-import org.opendaylight.netconf.mapping.api.HandlingPriority;
-import org.opendaylight.netconf.mapping.api.NetconfOperation;
-import org.opendaylight.netconf.mapping.api.NetconfOperationChainedExecution;
-import org.opendaylight.netconf.mapping.api.NetconfOperationService;
-import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
-import org.opendaylight.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
-import org.opendaylight.netconf.api.messages.NetconfHelloMessageAdditionalHeader;
-import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
-import org.opendaylight.netconf.util.test.XmlFileLoader;
import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.api.messages.NetconfHelloMessageAdditionalHeader;
import org.opendaylight.netconf.api.monitoring.CapabilityListener;
import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
import org.opendaylight.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory;
+import org.opendaylight.netconf.mapping.api.HandlingPriority;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationChainedExecution;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+import org.opendaylight.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
+import org.opendaylight.netconf.util.messages.NetconfMessageUtil;
+import org.opendaylight.netconf.util.test.XmlFileLoader;
import org.opendaylight.protocol.framework.NeverReconnectStrategy;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder;
SessionIdProvider idProvider = new SessionIdProvider();
- NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory(
- hashedWheelTimer, factoriesListener, idProvider, 5000, createMockedMonitoringService(), serverCaps);
+ NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactoryBuilder()
+ .setTimer(hashedWheelTimer)
+ .setAggregatedOpService(factoriesListener)
+ .setIdProvider(idProvider)
+ .setConnectionTimeoutMillis(5000)
+ .setMonitoringService(createMockedMonitoringService())
+ .setBaseCapabilities(serverCaps)
+ .build();
NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(serverNegotiatorFactory);
final NetconfServerDispatcherImpl dispatch = new NetconfServerDispatcherImpl(serverChannelInitializer, nettyGroup, nettyGroup);
+++ /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());
+ }
}
SessionIdProvider idProvider = new SessionIdProvider();
hashedWheelTimer = new HashedWheelTimer();
- NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory(
- hashedWheelTimer, factoriesListener, idProvider, 5000, ConcurrentClientsTest.createMockedMonitoringService());
+
+ NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactoryBuilder()
+ .setAggregatedOpService(factoriesListener)
+ .setTimer(hashedWheelTimer)
+ .setIdProvider(idProvider)
+ .setMonitoringService(ConcurrentClientsTest.createMockedMonitoringService())
+ .setConnectionTimeoutMillis(5000)
+ .build();
NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(serverNegotiatorFactory);
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import org.opendaylight.netconf.monitoring.MonitoringConstants;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.Session1;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.sessions.Session;
import org.opendaylight.yangtools.yang.common.QName;
@XmlElement(name = "source-host")
public String getSourceHost() {
- return managementSession.getSourceHost().getDomainName().getValue();
+ final IpAddress ipAddress = managementSession.getSourceHost().getIpAddress();
+ if (ipAddress.getIpv4Address() != null) {
+ return ipAddress.getIpv4Address().getValue();
+ } else {
+ return ipAddress.getIpv6Address().getValue();
+ }
}
@XmlElement(name = "login-time")
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import com.google.common.base.Optional;
import com.google.common.collect.Lists;
-import java.util.Set;
import org.hamcrest.CoreMatchers;
+import org.junit.Before;
import org.junit.Test;
-import org.opendaylight.controller.config.util.capability.Capability;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.opendaylight.controller.config.util.xml.XmlUtil;
-import org.opendaylight.netconf.api.monitoring.NetconfManagementSession;
import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.netconf.monitoring.xml.model.NetconfState;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.DomainName;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Host;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv6Address;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.NetconfTcp;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.Session1;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfSsh;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Transport;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.SchemasBuilder;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.SessionsBuilder;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.SchemaKey;
public class JaxBSerializerTest {
+ private static final String IPV4 = "192.168.1.1";
+ private static final String IPV6 = "FE80:0000:0000:0000:0202:B3FF:FE1E:8329";
+ private static final String SESSION_XML = "<session>" +
+ "<session-id>1</session-id>" +
+ "<in-bad-rpcs>0</in-bad-rpcs>" +
+ "<in-rpcs>0</in-rpcs>" +
+ "<login-time>2010-10-10T12:32:32Z</login-time>" +
+ "<out-notifications>0</out-notifications>" +
+ "<out-rpc-errors>0</out-rpc-errors>" +
+ "<ncme:session-identifier>client</ncme:session-identifier>" +
+ "<source-host>%s</source-host>" +
+ "<transport>ncme:netconf-tcp</transport>" +
+ "<username>username</username>" +
+ "</session>";
+
+ @Mock
+ private NetconfMonitoringService monitoringService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(new SessionsBuilder().setSession(Lists.newArrayList(
+ getMockIPv4Session(NetconfTcp.class),
+ getMockIPv4Session(NetconfSsh.class),
+ getMockIPv6Session(NetconfTcp.class),
+ getMockIPv6Session(NetconfSsh.class)
+ )).build())
+ .when(monitoringService).getSessions();
+ doReturn(new SchemasBuilder().setSchema(Lists.newArrayList(getMockSchema("id", "v1", Yang.class), getMockSchema("id2", "", Yang.class))).build())
+ .when(monitoringService).getSchemas();
+ }
+
@Test
public void testSerialization() throws Exception {
- final NetconfMonitoringService service = new NetconfMonitoringService() {
-
- @Override
- public void onCapabilitiesChanged(Set<Capability> added, Set<Capability> removed) {
-
- }
-
- @Override
- public void onSessionUp(final NetconfManagementSession session) {
-
- }
-
- @Override
- public void onSessionDown(final NetconfManagementSession session) {
-
- }
-
- @Override
- public Sessions getSessions() {
- return new SessionsBuilder().setSession(Lists.newArrayList(getMockSession(NetconfTcp.class), getMockSession(NetconfSsh.class))).build();
- }
-
- @Override
- public Schemas getSchemas() {
- return new SchemasBuilder().setSchema(Lists.newArrayList(getMockSchema("id", "v1", Yang.class), getMockSchema("id2", "", Yang.class))).build();
- }
-
- @Override
- public String getSchemaForCapability(final String moduleName, final Optional<String> revision) {
- return null;
- }
-
- @Override
- public Capabilities getCapabilities() {
- return null;
- }
-
- @Override
- public AutoCloseable registerListener(final MonitoringListener listener) {
- return new AutoCloseable() {
- @Override
- public void close() throws Exception {
- // NOOP
- }
- };
- }
- };
- final NetconfState model = new NetconfState(service);
+ final NetconfState model = new NetconfState(monitoringService);
final String xml = XmlUtil.toString(new JaxBSerializer().toXml(model)).replaceAll("\\s", "");
-
+ System.out.println(xml);
assertThat(xml, CoreMatchers.containsString(
"<schema>" +
"<format>yang</format>" +
"</schema>"));
assertThat(xml, CoreMatchers.containsString(
- "<session>" +
- "<session-id>1</session-id>" +
- "<in-bad-rpcs>0</in-bad-rpcs>" +
- "<in-rpcs>0</in-rpcs>" +
- "<login-time>2010-10-10T12:32:32Z</login-time>" +
- "<out-notifications>0</out-notifications>" +
- "<out-rpc-errors>0</out-rpc-errors>" +
- "<ncme:session-identifier>client</ncme:session-identifier>" +
- "<source-host>192.168.1.1</source-host>" +
- "<transport>ncme:netconf-tcp</transport>" +
- "<username>username</username>" +
- "</session>"));
+ String.format(SESSION_XML, IPV4)));
+ assertThat(xml, CoreMatchers.containsString(
+ String.format(SESSION_XML, IPV6)));
}
private Schema getMockSchema(final String id, final String version, final Class<Yang> format) {
return mock;
}
+ private Session getMockIPv4Session(final Class<? extends Transport> transportType) {
+ final Session mocked = getMockSession(transportType);
+ doReturn(new Host(new IpAddress(new Ipv4Address(IPV4)))).when(mocked).getSourceHost();
+ return mocked;
+ }
+
+ private Session getMockIPv6Session(final Class<? extends Transport> transportType) {
+ final Session mocked = getMockSession(transportType);
+ doReturn(new Host(new IpAddress(new Ipv6Address(IPV6)))).when(mocked).getSourceHost();
+ return mocked;
+ }
+
private Session getMockSession(final Class<? extends Transport> transportType) {
final Session mocked = mock(Session.class);
final Session1 mockedSession1 = mock(Session1.class);
doReturn("client").when(mockedSession1).getSessionIdentifier();
doReturn(1L).when(mocked).getSessionId();
doReturn(new DateAndTime("2010-10-10T12:32:32Z")).when(mocked).getLoginTime();
- doReturn(new Host(new DomainName("192.168.1.1"))).when(mocked).getSourceHost();
doReturn(new ZeroBasedCounter32(0L)).when(mocked).getInBadRpcs();
doReturn(new ZeroBasedCounter32(0L)).when(mocked).getInRpcs();
doReturn(new ZeroBasedCounter32(0L)).when(mocked).getOutNotifications();
private final Timer timer;
private final long connectionTimeoutMillis;
- // TODO shrink constructor
protected AbstractNetconfSessionNegotiator(final P sessionPreferences, final Promise<S> promise, final Channel channel, final Timer timer,
final L sessionListener, final long connectionTimeoutMillis) {
super(promise, channel);
<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;
- }
-}
if (!maybeFilter.isPresent()) {
return rpcReply;
}
-
- rpcReply = reReadDocument(rpcReply);
XmlElement filter = maybeFilter.get();
if (isSupported(filter)) {
* @throws DocumentedException
*/
public static Optional<Document> applySubtreeNotificationFilter(XmlElement filter, Document notification) throws DocumentedException {
- notification = reReadDocument(notification);
removeEventTimeNode(notification);
if (isSupported(filter)) {
return Optional.fromNullable(filteredNotification(filter, notification));
return Optional.of(extractNotificationContent(notification));
}
- private static Document reReadDocument(Document notification) throws DocumentedException {
- // FIXME: rpcReply document must be reread otherwise some nodes do not inherit namespaces. (services/service)
- try {
- notification = XmlUtil.readXmlToDocument(XmlUtil.toString(notification, true));
- } catch (SAXException | IOException e) {
- LOG.error("Cannot transform document", e);
- throw new DocumentedException("Cannot transform document" + e);
- }
- return notification;
- }
-
private static void removeEventTimeNode(Document document) {
final Node eventTimeNode = document.getDocumentElement().getElementsByTagNameNS(
XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_NOTIFICATION_1_0, XmlNetconfConstants.EVENT_TIME).item(0);
+++ /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>
import org.opendaylight.protocol.framework.TimedReconnectStrategy;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Host;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
+import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
private static final SchemaContextFactory DEFAULT_SCHEMA_CONTEXT_FACTORY =
DEFAULT_SCHEMA_REPOSITORY.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT);
+ private static final int LOCAL_IO_FALLBACK_COST = PotentialSchemaSource.Costs.LOCAL_IO.getValue() + 1;
+
/**
* Keeps track of initialized Schema resources. A Map is maintained in which the key represents the name
* of the schema cache directory, and the value is a corresponding <code>SchemaResourcesDTO</code>. The
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.
setSchemaRegistry(dto.getSchemaRegistry());
schemaResourcesDTO = dto;
}
+ if (userCapabilities.isPresent()) {
+ for (QName qname : userCapabilities.get().getModuleBasedCaps()) {
+ final SourceIdentifier sourceIdentifier = new SourceIdentifier(qname.getLocalName(), qname.getFormattedRevision());
+ dto.getSchemaRegistry().registerSchemaSource(DEFAULT_CACHE, PotentialSchemaSource.create(sourceIdentifier, YangTextSchemaSource.class, LOCAL_IO_FALLBACK_COST));
+ }
+ }
}
LOG.info("Netconf connector for device {} will use schema cache directory {} instead of {}",
instanceName, moduleSchemaCacheDirectory, DEFAULT_CACHE_DIRECTORY);
*/
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);
}
}
+++ /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.connect.netconf.sal;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.FluentIterable;
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-
-import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
-import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
-import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
-import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
-import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodesBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNode;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNodeBuilder;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Asynchronous (Binding-aware) adapter over datastore subtree for netconf device.
- *
- * All data changes are submitted to an ExecutorService to avoid Thread blocking while sal is waiting for schema.
- *
- * @deprecated Data is pushed into Topology instead if Inventory model
- */
-@Deprecated
-final class NetconfDeviceDatastoreAdapter implements AutoCloseable {
-
- private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceDatastoreAdapter.class);
-
- private final RemoteDeviceId id;
- private BindingTransactionChain txChain;
-
- NetconfDeviceDatastoreAdapter(final RemoteDeviceId deviceId, final BindingTransactionChain txChain) {
- this.id = Preconditions.checkNotNull(deviceId);
- this.txChain = Preconditions.checkNotNull(txChain);
-
- initDeviceData();
- }
-
- public void updateDeviceState(final boolean up, final Set<QName> capabilities) {
- final org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node data = buildDataForDeviceState(
- up, capabilities, id);
-
- final ReadWriteTransaction transaction = txChain.newReadWriteTransaction();
- logger.trace("{}: Update device state transaction {} merging operational data started.", id, transaction.getIdentifier());
- transaction.put(LogicalDatastoreType.OPERATIONAL, id.getBindingPath(), data);
- logger.trace("{}: Update device state transaction {} merging operational data ended.", id, transaction.getIdentifier());
-
- commitTransaction(transaction, "update");
- }
-
- private void removeDeviceConfigAndState() {
- final WriteTransaction transaction = txChain.newWriteOnlyTransaction();
- logger.trace("{}: Close device state transaction {} removing all data started.", id, transaction.getIdentifier());
- transaction.delete(LogicalDatastoreType.CONFIGURATION, id.getBindingPath());
- transaction.delete(LogicalDatastoreType.OPERATIONAL, id.getBindingPath());
- logger.trace("{}: Close device state transaction {} removing all data ended.", id, transaction.getIdentifier());
-
- try {
- transaction.submit().get();
- } catch (InterruptedException | ExecutionException e) {
- logger.error("{}: Transaction(close) {} FAILED!", id, transaction.getIdentifier(), e);
- throw new IllegalStateException(id + " Transaction(close) not committed correctly", e);
- }
- }
-
- private void initDeviceData() {
- final WriteTransaction transaction = txChain.newWriteOnlyTransaction();
-
- createNodesListIfNotPresent(transaction);
-
- final InstanceIdentifier<Node> path = id.getBindingPath();
- final Node nodeWithId = getNodeWithId(id);
-
- logger.trace("{}: Init device state transaction {} putting if absent operational data started.", id, transaction.getIdentifier());
- transaction.put(LogicalDatastoreType.OPERATIONAL, path, nodeWithId);
- logger.trace("{}: Init device state transaction {} putting operational data ended.", id, transaction.getIdentifier());
-
- logger.trace("{}: Init device state transaction {} putting if absent config data started.", id, transaction.getIdentifier());
- transaction.put(LogicalDatastoreType.CONFIGURATION, path, nodeWithId);
- logger.trace("{}: Init device state transaction {} putting config data ended.", id, transaction.getIdentifier());
-
- commitTransaction(transaction, "init");
- }
-
- private void createNodesListIfNotPresent(final WriteTransaction writeTx) {
- final Nodes nodes = new NodesBuilder().build();
- final InstanceIdentifier<Nodes> path = InstanceIdentifier.builder(Nodes.class).build();
- logger.trace("{}: Merging {} container to ensure its presence", id, Nodes.QNAME, writeTx.getIdentifier());
- writeTx.merge(LogicalDatastoreType.CONFIGURATION, path, nodes);
- writeTx.merge(LogicalDatastoreType.OPERATIONAL, path, nodes);
- }
-
- private void commitTransaction(final WriteTransaction transaction, final String txType) {
- logger.trace("{}: Committing Transaction {}:{}", id, txType, transaction.getIdentifier());
- final CheckedFuture<Void, TransactionCommitFailedException> result = transaction.submit();
-
- Futures.addCallback(result, new FutureCallback<Void>() {
- @Override
- public void onSuccess(final Void result) {
- logger.trace("{}: Transaction({}) {} SUCCESSFUL", id, txType, transaction.getIdentifier());
- }
-
- @Override
- public void onFailure(final Throwable t) {
- logger.error("{}: Transaction({}) {} FAILED!", id, txType, transaction.getIdentifier(), t);
- throw new IllegalStateException(id + " Transaction(" + txType + ") not committed correctly", t);
- }
- });
-
- }
-
- @Override
- public void close() throws Exception {
- removeDeviceConfigAndState();
- }
-
- public static org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node buildDataForDeviceState(
- final boolean up, final Set<QName> capabilities, final RemoteDeviceId id) {
-
- final NodeBuilder nodeBuilder = getNodeWithIdBuilder(id);
- final NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder();
- netconfNodeBuilder.setConnected(up);
- netconfNodeBuilder.setInitialCapability(FluentIterable.from(capabilities)
- .transform(new Function<QName, String>() {
- @Override
- public String apply(final QName input) {
- return input.toString();
- }
- }).toList());
- nodeBuilder.addAugmentation(NetconfNode.class, netconfNodeBuilder.build());
-
- return nodeBuilder.build();
- }
-
- private static ListenableFuture<Optional<Node>> readNodeData(
- final LogicalDatastoreType store,
- final ReadWriteTransaction transaction,
- final InstanceIdentifier<Node> path) {
- return transaction.read(store, path);
- }
-
- private static Node getNodeWithId(final RemoteDeviceId id) {
- final NodeBuilder nodeBuilder = getNodeWithIdBuilder(id);
- return nodeBuilder.build();
- }
-
- private static NodeBuilder getNodeWithIdBuilder(final RemoteDeviceId id) {
- final NodeBuilder nodeBuilder = new NodeBuilder();
- nodeBuilder.setKey(id.getBindingKey());
- nodeBuilder.setId(id.getBindingKey().getId());
- return nodeBuilder;
- }
-
- public void setTxChain(BindingTransactionChain txChain) {
- this.txChain = Preconditions.checkNotNull(txChain);
- }
-}
if (input.isSuccessful()) {
return transformer.toRpcResult(input.getResult(), type);
} else {
- // TODO check whether the listener sets errors properly
return new DefaultDOMRpcResult(input.getErrors());
}
}
@Nullable
@Override
public DOMRpcException apply(@Nullable final Exception e) {
- // FIXME what other possible exceptions are there ?
return new DOMRpcImplementationNotAvailableException(e, "Unable to invoke rpc %s", type);
}
});
package org.opendaylight.netconf.sal.connect.netconf.sal;
import com.google.common.collect.Lists;
-import java.util.Collections;
import java.util.List;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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();
- salProvider.getMountInstance().onDeviceConnected(schemaContext, domBroker, deviceRpc, notificationService);
- salProvider.getDatastoreAdapter().updateDeviceState(true, netconfSessionPreferences.getModuleBasedCaps());
salProvider.getMountInstance().onTopologyDeviceConnected(schemaContext, domBroker, deviceRpc, notificationService);
salProvider.getTopologyDatastoreAdapter().updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities());
}
@Override
public synchronized void onDeviceDisconnected() {
- salProvider.getDatastoreAdapter().updateDeviceState(false, Collections.<QName>emptySet());
salProvider.getTopologyDatastoreAdapter().updateDeviceData(false, new NetconfDeviceCapabilities());
- salProvider.getMountInstance().onDeviceDisconnected();
salProvider.getMountInstance().onTopologyDeviceDisconnected();
}
@Override
public synchronized void onDeviceFailed(final Throwable throwable) {
salProvider.getTopologyDatastoreAdapter().setDeviceAsFailed(throwable);
- salProvider.getMountInstance().onDeviceDisconnected();
salProvider.getMountInstance().onTopologyDeviceDisconnected();
}
private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceSalProvider.class);
private final RemoteDeviceId id;
- private volatile NetconfDeviceDatastoreAdapter datastoreAdapter;
private MountInstance mountInstance;
private volatile NetconfDeviceTopologyAdapter topologyDatastoreAdapter;
return mountInstance;
}
- public NetconfDeviceDatastoreAdapter getDatastoreAdapter() {
- Preconditions.checkState(datastoreAdapter != null,
- "%s: Sal provider %s was not initialized by sal. Cannot get datastore adapter", id);
- return datastoreAdapter;
- }
-
public NetconfDeviceTopologyAdapter getTopologyDatastoreAdapter() {
Preconditions.checkState(topologyDatastoreAdapter != null,
"%s: Sal provider %s was not initialized by sal. Cannot get topology datastore adapter", id);
this.dataBroker = session.getSALService(DataBroker.class);
txChain = Preconditions.checkNotNull(dataBroker).createTransactionChain(transactionChainListener);
- datastoreAdapter = new NetconfDeviceDatastoreAdapter(id, txChain);
topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(id, txChain);
}
private void resetTransactionChainForAdapaters() {
txChain = Preconditions.checkNotNull(dataBroker).createTransactionChain(transactionChainListener);
- datastoreAdapter.setTxChain(txChain);
topologyDatastoreAdapter.setTxChain(txChain);
logger.trace("{}: Resetting TransactionChain {}", id, txChain);
public void close() throws Exception {
mountInstance.close();
- datastoreAdapter.close();
- datastoreAdapter = null;
topologyDatastoreAdapter.close();
topologyDatastoreAdapter = null;
txChain.close();
private DOMMountPointService mountService;
private final RemoteDeviceId id;
- private ObjectRegistration<DOMMountPoint> registration;
private NetconfDeviceNotificationService notificationService;
private ObjectRegistration<DOMMountPoint> topologyRegistration;
this.id = Preconditions.checkNotNull(id);
}
- @Deprecated
- synchronized void onDeviceConnected(final SchemaContext initialCtx,
- final DOMDataBroker broker, final DOMRpcService rpc,
- final NetconfDeviceNotificationService notificationService) {
-
- Preconditions.checkNotNull(mountService, "Closed");
- Preconditions.checkState(registration == null, "Already initialized");
-
- final DOMMountPointService.DOMMountPointBuilder mountBuilder = mountService.createMountPoint(id.getPath());
- mountBuilder.addInitialSchemaContext(initialCtx);
-
- mountBuilder.addService(DOMDataBroker.class, broker);
- mountBuilder.addService(DOMRpcService.class, rpc);
- mountBuilder.addService(DOMNotificationService.class, notificationService);
- this.notificationService = notificationService;
-
- registration = mountBuilder.register();
- logger.debug("{}: Mountpoint exposed into MD-SAL {}", id, registration);
- }
-
- @Deprecated
- synchronized void onDeviceDisconnected() {
- if(registration == null) {
- logger.trace("{}: Not removing mountpoint from MD-SAL, mountpoint was not registered yet", id);
- return;
- }
-
- try {
- registration.close();
- } catch (final Exception e) {
- // Only log and ignore
- logger.warn("Unable to unregister mount instance for {}. Ignoring exception", id.getPath(), e);
- } finally {
- logger.debug("{}: Mountpoint removed from MD-SAL {}", id, registration);
- registration = null;
- }
- }
-
public synchronized void onTopologyDeviceConnected(final SchemaContext initialCtx,
final DOMDataBroker broker, final DOMRpcService rpc,
final NetconfDeviceNotificationService notificationService) {
mountBuilder.addService(DOMDataBroker.class, broker);
mountBuilder.addService(DOMRpcService.class, rpc);
mountBuilder.addService(DOMNotificationService.class, notificationService);
+ this.notificationService = notificationService;
topologyRegistration = mountBuilder.register();
logger.debug("{}: TOPOLOGY Mountpoint exposed into MD-SAL {}", id, topologyRegistration);
@Override
public synchronized void close() throws Exception {
- onDeviceDisconnected();
onTopologyDeviceDisconnected();
mountService = null;
}
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) {
final CheckedFuture<YangTextSchemaSource, SchemaSourceException> checked = Futures.makeChecked(transformed, MAPPER);
- // / FIXME remove this get, it is only present to wait until source is retrieved
- // (goal is to limit concurrent schema download, since NetconfDevice listener does not handle concurrent messages properly)
- // TODO retest this
- try {
- LOG.trace("{}: Blocking for {}", id, sourceIdentifier);
- checked.checkedGet();
- } catch (final SchemaSourceException e) {
- return Futures.immediateFailedCheckedFuture(e);
- }
-
return checked;
}
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);
mountInstance = new NetconfDeviceSalProvider.MountInstance(service, new RemoteDeviceId("device-1", InetSocketAddress.createUnresolved("localhost", 17830)));
}
- @Test
- public void testOnDeviceConnected() throws Exception {
- mountInstance.onDeviceConnected(SCHEMA_CONTEXT, broker, rpcService, notificationService);
- verify(mountPointBuilder).addInitialSchemaContext(SCHEMA_CONTEXT);
- verify(mountPointBuilder).addService(DOMDataBroker.class, broker);
- verify(mountPointBuilder).addService(DOMRpcService.class, rpcService);
- verify(mountPointBuilder).addService(DOMNotificationService.class, notificationService);
- }
-
- @Test
- public void testOnDeviceDisconnected() throws Exception {
- mountInstance.onDeviceConnected(SCHEMA_CONTEXT, broker, rpcService, notificationService);
- mountInstance.onDeviceDisconnected();
- verify(registration).close();
- try {
- mountInstance.onDeviceConnected(SCHEMA_CONTEXT, broker, rpcService, notificationService);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- Assert.fail("Registration still present after disconnect ");
- }
- }
@Test
public void testOnTopologyDeviceConnected() throws Exception {
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
</dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ <version>2.6.1</version>
+ </dependency>
<dependency>
<groupId>net.sourceforge.argparse4j</groupId>
<artifactId>argparse4j</artifactId>
package org.opendaylight.netconf.test.tool;
import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.util.ContextInitializer;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter;
import com.google.common.base.Stopwatch;
import com.google.common.io.CharStreams;
import com.ning.http.client.AsyncHttpClient;
import org.slf4j.LoggerFactory;
public class ScaleUtil {
-
- private static final Logger RESULTS_LOG = LoggerFactory.getLogger("results");
+ private static Logger RESULTS_LOG ;
private static final ScheduledExecutorService executor = new LoggingWrapperExecutor(4);
private static final int deviceStep = 1000;
public static void main(final String[] args) {
final TesttoolParameters params = TesttoolParameters.parseArgs(args, TesttoolParameters.getParser());
- root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
- root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
+ setUpLoggers(params);
// cleanup at the start in case controller was already running
final Runtime runtime = Runtime.getRuntime();
}
}
+ private static void setUpLoggers(final TesttoolParameters params) {
+ System.setProperty("log_file_name", "scale-util.log");
+
+ root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
+ RESULTS_LOG = LoggerFactory.getLogger("results");
+ }
+
private static void cleanup(final Runtime runtime, final TesttoolParameters params) {
try {
stopKaraf(runtime, params);
import java.util.Set;
import org.opendaylight.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.netconf.impl.NetconfServerSessionNegotiatorFactory;
+import org.opendaylight.netconf.impl.NetconfServerSessionNegotiatorFactoryBuilder;
import org.opendaylight.netconf.impl.SessionIdProvider;
import org.opendaylight.netconf.mapping.api.NetconfOperationService;
import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
public TesttoolNegotiationFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider,
final SessionIdProvider idProvider, final long connectionTimeoutMillis,
final NetconfMonitoringService monitoringService) {
- super(timer, netconfOperationProvider, idProvider, connectionTimeoutMillis, monitoringService);
+ super(timer, netconfOperationProvider, idProvider, connectionTimeoutMillis, monitoringService, NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES);
}
public TesttoolNegotiationFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider,
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);
}
<appender-ref ref="STDOUT"/>
</root>
- <appender name="RESULTS-FILE" class="ch.qos.logback.core.FileAppender">
- <file>scale-results.log</file>
- <append>true</append>
- <encoder>
- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
- </encoder>
- </appender>
+ <if condition='property("log_file_name").contains("scale-util.log")'>
+ <then>
+ <appender name="RESULTS-FILE" class="ch.qos.logback.core.FileAppender">
+ <file>${log_file_name}</file>
+ <append>true</append>
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
- <logger name="results" level="DEBUG" additivity="false">
- <appender-ref ref="RESULTS-FILE"/>
- </logger>
+ <logger name="results" level="DEBUG" additivity="false">
+ <appender-ref ref="RESULTS-FILE"/>
+ </logger>
+ </then>
+ </if>
<logger name="com.ning.http.client" level="WARN"/>
</configuration>
\ No newline at end of file
<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;
}
targetNode = findInstanceDataChildByNameAndNamespace(parentNode, nodeName, module.getNamespace());
if (targetNode == null && parentNode instanceof Module) {
- final RpcDefinition rpc = ControllerContext.getInstance().getRpcDefinition(head, module.getRevision());
+ final RpcDefinition rpc;
+ if (mountPoint == null) {
+ rpc = ControllerContext.getInstance().getRpcDefinition(head, module.getRevision());
+ } else {
+ final String rpcName = toNodeName(head);
+ rpc = ControllerContext.getInstance().getRpcDefinition(module, rpcName);
+ }
if (rpc != null) {
return new InstanceIdentifierContext<RpcDefinition>(builder.build(), rpc, mountPoint,
mountPoint != null ? mountPoint.getSchemaContext() : globalSchema);
return validName == null ? null : qnameToRpc.get().get(validName);
}
+ private RpcDefinition getRpcDefinition(final Module module, final String rpcName) {
+ QName rpcQName = QName.create(module.getQNameModule(), rpcName);
+ for (RpcDefinition rpcDefinition : module.getRpcs()) {
+ if (rpcQName.equals(rpcDefinition.getQName())) {
+ return rpcDefinition;
+ }
+ }
+ return null;
+ }
+
@Override
public void onGlobalContextUpdated(final SchemaContext context) {
if (context != null) {
--- /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
builder.append(moduleName);
builder.append(':');
}
- boolean first = true;
for (PathArgument arg : key.getPathArguments()) {
-
String name = arg.getNodeType().getLocalName();
- if (first) {
- first = false;
- } else {
- builder.append('/');
- }
- builder.append(name);
if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
NodeIdentifierWithPredicates nodeId = (NodeIdentifierWithPredicates) arg;
for (Entry<QName, Object> entry : nodeId.getKeyValues().entrySet()) {
- builder.append('/').append(entry.getValue());
+ builder.append(entry.getValue()).append('/');
}
+ } else {
+ builder.append(name);
+ builder.append('/');
}
}
-
- return builder.append('/').toString();
+ return builder.toString();
}
private String getYangMountUrl(final YangInstanceIdentifier key) {
private static final String HTTP_URL = "http://localhost/path";
private static final YangInstanceIdentifier instanceId = YangInstanceIdentifier.builder()
.node(QName.create("nodes"))
+ .node(QName.create("node"))
.nodeWithKey(QName.create("node"), QName.create("id"), "123").build();
private static final String INSTANCE_URL = "nodes/node/123/";
private MountPointSwagger swagger;