Merge "Check Testool YANG filenames for revision"
authorTomas Cere <tcere@cisco.com>
Tue, 4 Oct 2016 08:41:05 +0000 (08:41 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 4 Oct 2016 08:41:05 +0000 (08:41 +0000)
49 files changed:
features/restconf/pom.xml
features/restconf/src/main/features/features.xml
netconf/config-netconf-connector/src/test/java/org/opendaylight/netconf/confignetconfconnector/osgi/NetconfOperationServiceImplTest.java [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/EditConfig.java
netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/NetconfMDSalMappingTest.java
netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/editConfigs/edit-config-merge-map-entry.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/editConfigs/edit-config-replace-map-entry.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/get-config-map-entry.xml [new file with mode: 0644]
netconf/mdsal-netconf-notification/src/test/java/org/opendaylight/controller/config/yang/netconf/mdsal/notification/CapabilityChangeNotificationProducerTest.java
netconf/netconf-api/src/test/java/org/opendaylight/netconf/api/monitoring/SessionEventTest.java [new file with mode: 0644]
netconf/netconf-notifications-impl/src/main/java/org/opendaylight/netconf/notifications/impl/osgi/Activator.java
netconf/netconf-notifications-impl/src/test/java/org/opendaylight/netconf/notifications/impl/osgi/ActivatorTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/pom.xml
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/SchemalessNetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/SchemalessNetconfDeviceTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapterTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/util/RemoteDeviceIdTest.java [new file with mode: 0644]
netconf/yanglib/pom.xml
netconf/yanglib/src/main/java/org/opendaylight/yanglib/impl/YangLibServiceImpl.java
netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibRestAppTest.java [new file with mode: 0644]
netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibServiceImplTest.java [new file with mode: 0644]
pom.xml
restconf/sal-rest-connector/pom.xml
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/StringModuleInstanceIdentifierCodec.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestCodec.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/patch/Draft16StringModuleInstanceIdentifierCodec.java
restconf/sal-rest-connector/src/main/yang/opendaylight-md-sal-remote.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/InstanceIdentifierTypeLeafTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/AbstractBodyReaderTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReaderMountPoint.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReader.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlBodyReaderMountPoint.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReader.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReaderMountPoint.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtilTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/iid-value.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlDataFindBarContainer.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlDataFindFooContainer.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/bar-module.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/foo-module.yang [new file with mode: 0644]
restconf/sal-restconf-broker/pom.xml

index c4eb0e19cebb570cdfd3cf6fdfc8ac4365f87614..45be17258529c7a5c1b985ffaf454f927e357ce5 100644 (file)
       <classifier>features</classifier>
       <type>xml</type>
     </dependency>
-    <dependency>
-      <groupId>org.opendaylight.controller</groupId>
-      <artifactId>sal-remote</artifactId>
-      <version>${controller.mdsal.version}</version>
-    </dependency>
 
     <dependency>
       <groupId>org.opendaylight.netconf</groupId>
index 2855b6176c9b83964253a4907b5bff3b0305e45d..fc13c74f34ca993fb6820d3decacf70ee93debd7 100644 (file)
@@ -30,9 +30,6 @@
         <feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
         <feature version='[4.0.30,4.1.0)'>odl-netty</feature>
         <feature>war</feature>
-        <!-- presently we need sal-remote to be listed BEFORE sal-rest-connector because sal-rest-connector
-             has a yang file which augments a yang file in sal-remote, and order seems to matter -->
-        <bundle>mvn:org.opendaylight.controller/sal-remote/{{VERSION}}</bundle>
         <bundle>mvn:org.opendaylight.netconf/sal-rest-connector/{{VERSION}}</bundle>
         <bundle>mvn:com.google.code.gson/gson/{{VERSION}}</bundle>
         <bundle>mvn:org.opendaylight.yangtools/yang-data-codec-gson/{{VERSION}}</bundle>
diff --git a/netconf/config-netconf-connector/src/test/java/org/opendaylight/netconf/confignetconfconnector/osgi/NetconfOperationServiceImplTest.java b/netconf/config-netconf-connector/src/test/java/org/opendaylight/netconf/confignetconfconnector/osgi/NetconfOperationServiceImplTest.java
new file mode 100644 (file)
index 0000000..f286bd3
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.confignetconfconnector.osgi;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Set;
+import org.junit.Test;
+import org.opendaylight.controller.config.facade.xml.ConfigSubsystemFacade;
+import org.opendaylight.netconf.confignetconfconnector.operations.Commit;
+import org.opendaylight.netconf.confignetconfconnector.operations.DiscardChanges;
+import org.opendaylight.netconf.confignetconfconnector.operations.Lock;
+import org.opendaylight.netconf.confignetconfconnector.operations.UnLock;
+import org.opendaylight.netconf.confignetconfconnector.operations.Validate;
+import org.opendaylight.netconf.confignetconfconnector.operations.editconfig.EditConfig;
+import org.opendaylight.netconf.confignetconfconnector.operations.get.Get;
+import org.opendaylight.netconf.confignetconfconnector.operations.getconfig.GetConfig;
+import org.opendaylight.netconf.confignetconfconnector.operations.runtimerpc.RuntimeRpc;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+
+public class NetconfOperationServiceImplTest {
+
+    @Test
+    public void testOperationService() {
+        final ConfigSubsystemFacade configSubsystemFacade = mock(ConfigSubsystemFacade.class);
+        final NetconfOperationService netconfOperationService =
+                new NetconfOperationServiceImpl(configSubsystemFacade, "reportingID");
+
+        // testing operations in Set from NetconfOperationProvider
+
+        Set<NetconfOperation> operations = netconfOperationService.getNetconfOperations();
+
+        assertTrue(containInstance(operations, GetConfig.class));
+        assertTrue(containInstance(operations, EditConfig.class));
+        assertTrue(containInstance(operations, Commit.class));
+        assertTrue(containInstance(operations, Lock.class));
+        assertTrue(containInstance(operations, UnLock.class));
+        assertTrue(containInstance(operations, Get.class));
+        assertTrue(containInstance(operations, DiscardChanges.class));
+        assertTrue(containInstance(operations, Validate.class));
+        assertTrue(containInstance(operations, RuntimeRpc.class));
+
+        // verify closing service
+
+        doNothing().when(configSubsystemFacade).close();
+        netconfOperationService.close();
+
+        verify(configSubsystemFacade, times(1)).close();
+    }
+
+    private boolean containInstance(final Set<NetconfOperation> operations, final Class<?> cls) {
+        return operations.stream().filter(cls::isInstance).findFirst().isPresent();
+    }
+}
index 0d569e60121b2db634a281faf9f8973787a4f2e9..0a90d9c9d47aa980403da8fde1839acc03e7899b 100644 (file)
@@ -32,7 +32,10 @@ import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 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;
@@ -76,7 +79,7 @@ public class EditConfig extends AbstractSingletonNetconfOperation {
 
         final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
 
-        for (XmlElement element : configElement.getChildElements()) {
+        for (final XmlElement element : configElement.getChildElements()) {
             final String ns = element.getNamespace();
             final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
 
@@ -102,45 +105,70 @@ public class EditConfig extends AbstractSingletonNetconfOperation {
     }
 
     private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change) throws DocumentedException {
+        final YangInstanceIdentifier path = YangInstanceIdentifier.create(change.getPath());
+        final NormalizedNode<?, ?> changeData = change.getChangeRoot();
         switch (change.getAction()) {
         case NONE:
             return;
         case MERGE:
-            rwtx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+            createMapNodeIfNonExistent(rwtx, path, changeData);
+            rwtx.merge(LogicalDatastoreType.CONFIGURATION, path, changeData);
             break;
         case CREATE:
             try {
-                final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
+                final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
                 if (readResult.isPresent()) {
                     throw new DocumentedException("Data already exists, cannot execute CREATE operation", ErrorType.protocol, ErrorTag.data_exists, ErrorSeverity.error);
                 }
-                rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
-            } catch (ReadFailedException e) {
+                createMapNodeIfNonExistent(rwtx, path, changeData);
+                rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
+            } catch (final ReadFailedException e) {
                 LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
             }
             break;
         case REPLACE:
-            rwtx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()), change.getChangeRoot());
+            createMapNodeIfNonExistent(rwtx, path, changeData);
+            rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
             break;
         case DELETE:
             try {
-                final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath())).checkedGet();
+                final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
                 if (!readResult.isPresent()) {
                     throw new DocumentedException("Data is missing, cannot execute DELETE operation", ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
                 }
-                rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
-            } catch (ReadFailedException e) {
+                rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
+            } catch (final ReadFailedException e) {
                 LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
             }
             break;
         case REMOVE:
-            rwtx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create(change.getPath()));
+            rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
             break;
         default:
             LOG.warn("Unknown/not implemented operation, not executing");
         }
     }
 
+    private void createMapNodeIfNonExistent(final DOMDataReadWriteTransaction rwtx, final YangInstanceIdentifier path,
+                                            final NormalizedNode change) throws DocumentedException {
+        if (!(change instanceof MapEntryNode)) {
+            return;
+        }
+        final YangInstanceIdentifier mapNodeYid = path.getParent();
+        try {
+            final Boolean mapNodeExists = rwtx.exists(LogicalDatastoreType.CONFIGURATION, mapNodeYid).checkedGet();
+            if (!mapNodeExists) {
+                final MapNode mixinNode = Builders.mapBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(mapNodeYid.getLastPathArgument().getNodeType()))
+                        .build();
+                rwtx.put(LogicalDatastoreType.CONFIGURATION, mapNodeYid, mixinNode);
+            }
+        } catch (final ReadFailedException e) {
+            throw new DocumentedException("List node existence check failed", e, ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error);
+
+        }
+    }
+
     private NormalizedNode parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
                                                    final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider) {
 
@@ -172,7 +200,7 @@ public class EditConfig extends AbstractSingletonNetconfOperation {
                 throw new NetconfDocumentedException("Unable to find module by namespace: " + namespace,
                         ErrorType.application, ErrorTag.unknown_namespace, ErrorSeverity.error);
             }
-            DataSchemaNode schemaNode =
+            final DataSchemaNode schemaNode =
                     module.getDataChildByName(QName.create(module.getQNameModule(), element.getName()));
             if (schemaNode != null) {
                 dataSchemaNode = Optional.of(schemaNode);
@@ -182,7 +210,7 @@ public class EditConfig extends AbstractSingletonNetconfOperation {
                         ErrorTag.unknown_namespace,
                         ErrorSeverity.error);
             }
-        } catch (URISyntaxException e) {
+        } catch (final URISyntaxException e) {
             LOG.debug("Unable to create URI for namespace : {}", namespace);
         }
 
@@ -215,7 +243,7 @@ public class EditConfig extends AbstractSingletonNetconfOperation {
 
     }
 
-    private XmlElement getElement(final XmlElement operationElement, String elementName) throws DocumentedException {
+    private XmlElement getElement(final XmlElement operationElement, final String elementName) throws DocumentedException {
         final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
         if (!childNode.isPresent()) {
             throw new DocumentedException(elementName + " element is missing",
index 11c8473606401e541355a8e7b8eaf98e2521e0d0..623162d74bf1f48d2b6deb651ee109e96f9c74df 100644 (file)
@@ -468,6 +468,20 @@ public class NetconfMDSalMappingTest {
         deleteDatastore();
     }
 
+    @Test
+    public void testReplaceMapEntry() throws Exception {
+        verifyResponse(edit("messages/mapping/editConfigs/edit-config-replace-map-entry.xml"), RPC_REPLY_OK);
+        verifyResponse(commit(), RPC_REPLY_OK);
+        verifyResponse(getConfigRunning(), XmlFileLoader.xmlFileToDocument("messages/mapping/get-config-map-entry.xml"));
+    }
+
+    @Test
+    public void testMergeMapEntry() throws Exception {
+        verifyResponse(edit("messages/mapping/editConfigs/edit-config-merge-map-entry.xml"), RPC_REPLY_OK);
+        verifyResponse(commit(), RPC_REPLY_OK);
+        verifyResponse(getConfigRunning(), XmlFileLoader.xmlFileToDocument("messages/mapping/get-config-map-entry.xml"));
+    }
+
     @Test
     public void testFiltering() throws Exception {
 
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/editConfigs/edit-config-merge-map-entry.xml b/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/editConfigs/edit-config-merge-map-entry.xml
new file mode 100644 (file)
index 0000000..870ec0a
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+  ~ 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
+  -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <edit-config>
+        <target>
+            <candidate/>
+        </target>
+        <default-operation>none</default-operation>
+        <config>
+            <mapping-nodes xmlns="urn:opendaylight:mdsal:mapping:test">
+                <mapping-node xmlns:a="urn:ietf:params:xml:ns:netconf:base:1.0" a:operation="merge">
+                    <id>id1</id>
+                    <content>content1</content>
+                </mapping-node>
+            </mapping-nodes>
+        </config>
+    </edit-config>
+</rpc>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/editConfigs/edit-config-replace-map-entry.xml b/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/editConfigs/edit-config-replace-map-entry.xml
new file mode 100644 (file)
index 0000000..d3dc541
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+  ~ 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
+  -->
+
+<rpc message-id="a" a="64" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <edit-config>
+        <target>
+            <candidate/>
+        </target>
+        <default-operation>none</default-operation>
+        <config>
+            <mapping-nodes xmlns="urn:opendaylight:mdsal:mapping:test">
+                <mapping-node xmlns:a="urn:ietf:params:xml:ns:netconf:base:1.0" a:operation="replace">
+                    <id>id1</id>
+                    <content>content1</content>
+                </mapping-node>
+            </mapping-nodes>
+        </config>
+    </edit-config>
+</rpc>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/get-config-map-entry.xml b/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/get-config-map-entry.xml
new file mode 100644 (file)
index 0000000..393f336
--- /dev/null
@@ -0,0 +1,18 @@
+<!--
+  ~ 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
+  -->
+
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" a="64" id="a" message-id="101" xmlnx="a:b:c:d">
+    <data>
+        <mapping-nodes xmlns="urn:opendaylight:mdsal:mapping:test">
+            <mapping-node>
+                <id>id1</id>
+                <content>content1</content>
+            </mapping-node>
+        </mapping-nodes>
+    </data>
+</rpc-reply>
\ No newline at end of file
index 8149ea00ff0b5e5e20b584798a6dbb899142bff1..e2156413bcd178af85de108ff27ff84682940d60 100644 (file)
@@ -57,7 +57,8 @@ public class CapabilityChangeNotificationProducerTest {
         Capabilities newCapabilities = new CapabilitiesBuilder().setCapability(newCapabilitiesList).build();
         Map<InstanceIdentifier<?>, DataObject> createdData = Maps.newHashMap();
         createdData.put(capabilitiesIdentifier, newCapabilities);
-        verifyDataTreeChange(null, newCapabilities, changedCapabilitesFrom(newCapabilitiesList, Collections.<Uri>emptyList()));
+        verifyDataTreeChange(DataObjectModification.ModificationType.WRITE, null, newCapabilities,
+                changedCapabilitesFrom(newCapabilitiesList, Collections.<Uri>emptyList()));
     }
 
     @Test
@@ -66,16 +67,27 @@ public class CapabilityChangeNotificationProducerTest {
         final List<Uri> updatedCapabilitiesList = Lists.newArrayList(new Uri("originalCapability"), new Uri("newCapability"));
         Capabilities originalCapabilities = new CapabilitiesBuilder().setCapability(originalCapabilitiesList).build();
         Capabilities updatedCapabilities = new CapabilitiesBuilder().setCapability(updatedCapabilitiesList).build();
-        verifyDataTreeChange(originalCapabilities, updatedCapabilities, changedCapabilitesFrom(
+        verifyDataTreeChange(DataObjectModification.ModificationType.WRITE, originalCapabilities, updatedCapabilities, changedCapabilitesFrom(
                 Lists.newArrayList(new Uri("newCapability")), Lists.newArrayList(new Uri("anotherOriginalCapability"))));
     }
 
+    @Test
+    public void testOnDataChangedDelete() {
+        final List<Uri> originalCapabilitiesList = Lists.newArrayList(new Uri("originalCapability"),
+                new Uri("anotherOriginalCapability"));
+        final Capabilities originalCapabilities =
+                new CapabilitiesBuilder().setCapability(originalCapabilitiesList).build();
+        verifyDataTreeChange(DataObjectModification.ModificationType.DELETE, originalCapabilities, null,
+                changedCapabilitesFrom(Collections.emptyList(), originalCapabilitiesList));
+    }
+
     @SuppressWarnings("unchecked")
-    private void verifyDataTreeChange(Capabilities originalCapabilities, Capabilities updatedCapabilities,
+    private void verifyDataTreeChange(final DataObjectModification.ModificationType modificationType,
+                                      Capabilities originalCapabilities, Capabilities updatedCapabilities,
                                       NetconfCapabilityChange expectedChange) {
         final DataTreeModification<Capabilities> treeChange2 = mock(DataTreeModification.class);
         final DataObjectModification<Capabilities> objectChange2 = mock(DataObjectModification.class);
-        doReturn(DataObjectModification.ModificationType.WRITE).when(objectChange2).getModificationType();
+        doReturn(modificationType).when(objectChange2).getModificationType();
         doReturn(objectChange2).when(treeChange2).getRootNode();
         doReturn(originalCapabilities).when(objectChange2).getDataBefore();
         doReturn(updatedCapabilities).when(objectChange2).getDataAfter();
diff --git a/netconf/netconf-api/src/test/java/org/opendaylight/netconf/api/monitoring/SessionEventTest.java b/netconf/netconf-api/src/test/java/org/opendaylight/netconf/api/monitoring/SessionEventTest.java
new file mode 100644 (file)
index 0000000..da4e4b2
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.api.monitoring;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+public class SessionEventTest {
+
+    @Test
+    public void test() {
+        final NetconfManagementSession session = mock(NetconfManagementSession.class);
+
+        assertEquals(SessionEvent.Type.IN_RPC_FAIL, SessionEvent.inRpcFail(session).getType());
+        assertEquals(SessionEvent.Type.IN_RPC_SUCCESS, SessionEvent.inRpcSuccess(session).getType());
+        assertEquals(SessionEvent.Type.NOTIFICATION, SessionEvent.notification(session).getType());
+        assertEquals(SessionEvent.Type.OUT_RPC_ERROR, SessionEvent.outRpcError(session).getType());
+    }
+}
index 89aee08e87f22476577928136c5f628647e9c3b1..1cbd5237389dad5900da775ce0458399ecb20721 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.netconf.notifications.impl.osgi;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.Dictionary;
@@ -107,4 +108,9 @@ public class Activator implements BundleActivator {
             operationaServiceRegistration = null;
         }
     }
+
+    @VisibleForTesting
+    NetconfNotificationManager getNetconfNotificationManager() {
+        return netconfNotificationManager;
+    }
 }
diff --git a/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/netconf/notifications/impl/osgi/ActivatorTest.java b/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/netconf/notifications/impl/osgi/ActivatorTest.java
new file mode 100644 (file)
index 0000000..24e8652
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.notifications.impl.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.controller.config.util.capability.BasicCapability;
+import org.opendaylight.controller.config.util.capability.Capability;
+import org.opendaylight.netconf.api.monitoring.CapabilityListener;
+import org.opendaylight.netconf.api.util.NetconfConstants;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+import org.opendaylight.netconf.notifications.NetconfNotification;
+import org.opendaylight.netconf.notifications.NetconfNotificationCollector;
+import org.opendaylight.netconf.notifications.impl.NetconfNotificationManager;
+import org.opendaylight.netconf.notifications.impl.ops.CreateSubscription;
+import org.opendaylight.netconf.notifications.impl.ops.Get;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+public class ActivatorTest {
+
+    @Test
+    public void testActivator() throws Exception {
+        final Activator activator = new Activator();
+        final BundleContext context = mock(BundleContext.class);
+
+
+        final ServiceRegistration netconfNotificationCollectorServiceRegistration  = mock(ServiceRegistration.class);
+        final ServiceRegistration operationaServiceRegistration = mock(ServiceRegistration.class);
+
+        // test registering services
+        doReturn(netconfNotificationCollectorServiceRegistration).when(context).
+                registerService(eq(NetconfNotificationCollector.class), any(NetconfNotificationManager.class), any());
+        doReturn(operationaServiceRegistration).when(context).
+                registerService(eq(NetconfOperationServiceFactory.class), any(NetconfOperationServiceFactory.class), any());
+
+        activator.start(context);
+
+        verify(context, times(1)).registerService(eq(NetconfNotificationCollector.class),
+                any(NetconfNotificationManager.class), eq(new Hashtable<>()));
+
+        final ArgumentCaptor<NetconfOperationServiceFactory> serviceFactoryArgumentCaptor =
+                ArgumentCaptor.forClass(NetconfOperationServiceFactory.class);
+
+        final Dictionary<String, String> properties = new Hashtable<>();
+        properties.put(NetconfConstants.SERVICE_NAME, NetconfConstants.NETCONF_MONITORING);
+
+        verify(context, times(1)).registerService(eq(NetconfOperationServiceFactory.class),
+                serviceFactoryArgumentCaptor.capture(), eq(properties));
+
+        // test service factory argument requisites
+        final NetconfOperationServiceFactory serviceFactory = serviceFactoryArgumentCaptor.getValue();
+
+        final Set<Capability> capabilities = Collections.singleton(new BasicCapability(NetconfNotification.NOTIFICATION_NAMESPACE));
+
+        assertEquals(capabilities.iterator().next().getCapabilityUri(), serviceFactory.getCapabilities().iterator().next().getCapabilityUri());
+        assertEquals(capabilities.iterator().next().getCapabilitySchema(), serviceFactory.getCapabilities().iterator().next().getCapabilitySchema());
+        assertEquals(capabilities.iterator().next().getModuleNamespace(), serviceFactory.getCapabilities().iterator().next().getModuleNamespace());
+        assertEquals(capabilities.iterator().next().getModuleName(), serviceFactory.getCapabilities().iterator().next().getModuleName());
+
+        final CapabilityListener listener = mock(CapabilityListener.class);
+
+        doNothing().when(listener).onCapabilitiesChanged(any(), any());
+
+        serviceFactory.registerCapabilityListener(listener);
+
+        verify(listener).onCapabilitiesChanged(serviceFactory.getCapabilities(), Collections.emptySet());
+
+        final NetconfOperationService netconfOperationService = serviceFactory.createService("id");
+        final Set<NetconfOperation> netconfOperations = netconfOperationService.getNetconfOperations();
+
+        final CreateSubscription createSubscription = new CreateSubscription("id", activator.getNetconfNotificationManager());
+
+        netconfOperations.forEach(
+                operation -> {
+                    if (operation instanceof CreateSubscription) {
+                        assertEquals(createSubscription.toString(), operation.toString());
+                    }
+                    if (operation instanceof Get) {
+                        assertEquals("id", ((Get) operation).getNetconfSessionIdForReporting());
+                    }
+                }
+        );
+
+        // test unregister after stop
+        doNothing().when(netconfNotificationCollectorServiceRegistration).unregister();
+        doNothing().when(operationaServiceRegistration).unregister();
+
+        activator.stop(context);
+
+        verify(netconfNotificationCollectorServiceRegistration, times(1)).unregister();
+        verify(operationaServiceRegistration, times(1)).unregister();
+
+    }
+
+
+}
index 7a91d064e51775ec7715e883614d582bd41b16e7..9adcf03eb64f716cc55dd7b152132a5d7b9c3b2a 100644 (file)
       <artifactId>sal-distributed-datastore</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <scm>
index 758c46c80df8a9d73a6160a7dd988fe26c145f25..5a7e64c0f58aa54f489eb3b6fd38af2e44c2d95b 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.netconf.sal.connect.netconf;
 
+import com.google.common.annotations.VisibleForTesting;
 import org.opendaylight.netconf.api.NetconfMessage;
 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
@@ -36,6 +37,16 @@ public class SchemalessNetconfDevice implements
         messageTransformer = new SchemalessMessageTransformer(counter);
     }
 
+    @VisibleForTesting
+    SchemalessNetconfDevice(final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
+                            final SchemalessMessageTransformer messageTransformer) {
+        this.id = id;
+        this.salFacade = salFacade;
+        final MessageCounter counter = new MessageCounter();
+        rpcTransformer = new BaseRpcSchemalessTransformer(counter);
+        this.messageTransformer = messageTransformer;
+    }
+
     @Override public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
                                             final NetconfDeviceCommunicator netconfDeviceCommunicator) {
         final SchemalessNetconfDeviceRpc schemalessNetconfDeviceRpc = new SchemalessNetconfDeviceRpc(id,
index 4fbd6f624145eac163c8bdb2cc61b3384782907d..93ac945f2d2c9c9cc8479796ada6a27906bab102 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import org.opendaylight.controller.config.util.xml.XmlElement;
@@ -63,6 +64,17 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
     private Future<?> initFuture;
     private SettableFuture<NetconfDeviceCapabilities> firstConnectionFuture;
 
+    // isSessionClosing indicates a close operation on the session is issued and
+    // tearDown will surely be called later to finish the close.
+    // Used to allow only one thread to enter tearDown and other threads should
+    // NOT enter it simultaneously and should end its close operation without
+    // calling tearDown to release the locks they hold to avoid deadlock.
+    private volatile AtomicBoolean isSessionClosing = new AtomicBoolean(false);
+
+    public Boolean isSessionClosing() {
+        return isSessionClosing.get();
+    }
+
     public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> remoteDevice,
             final UserPreferences NetconfSessionPreferences, final int rpcMessageLimit) {
         this(id, remoteDevice, Optional.of(NetconfSessionPreferences), rpcMessageLimit);
@@ -148,12 +160,16 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
     }
 
     public void disconnect() {
-        if(session != null) {
+        // If session is already in closing, no need to close it again
+        if(session != null && isSessionClosing.compareAndSet(false, true)) {
             session.close();
         }
     }
 
     private void tearDown( String reason ) {
+        if (!isSessionClosing()) {
+            LOG.warn("It's curious that no one to close the session but tearDown is called!");
+        }
         LOG.debug("Tearing down {}", reason);
         List<UncancellableFuture<RpcResult<NetconfMessage>>> futuresToCancel = Lists.newArrayList();
         sessionLock.lock();
@@ -193,6 +209,8 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
                 future.set( createErrorRpcResult( RpcError.ErrorType.TRANSPORT, reason ) );
             }
         }
+
+        isSessionClosing.set(false);
     }
 
     private RpcResult<NetconfMessage> createSessionDownRpcResult() {
@@ -207,12 +225,16 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
 
     @Override
     public void onSessionDown(final NetconfClientSession session, final Exception e) {
-        LOG.warn("{}: Session went down", id, e);
-        tearDown( null );
+        // If session is already in closing, no need to call tearDown again.
+        if (isSessionClosing.compareAndSet(false, true)) {
+            LOG.warn("{}: Session went down", id, e);
+            tearDown( null );
+        }
     }
 
     @Override
     public void onSessionTerminated(final NetconfClientSession session, final NetconfTerminationReason reason) {
+        // onSessionTerminated is called directly by disconnect, no need to compare and set isSessionClosing.
         LOG.warn("{}: Session terminated {}", id, reason);
         tearDown( reason.getErrorMessage() );
     }
index 829487a409a232c7ab040fbe6eee013047b7c613..90408690f33d008960856597a611ed7db2988e7b 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.netconf.sal.connect.netconf.sal;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import java.util.List;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
@@ -37,6 +38,14 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice
         registerToSal(domBroker, bindingBroker);
     }
 
+    @VisibleForTesting
+    NetconfDeviceSalFacade(final RemoteDeviceId id, NetconfDeviceSalProvider salProvider,
+                           final Broker domBroker, final BindingAwareBroker bindingBroker) {
+        this.id = id;
+        this.salProvider = salProvider;
+        registerToSal(domBroker, bindingBroker);
+    }
+
     public void registerToSal(final Broker domRegistryDependency, final BindingAwareBroker bindingBroker) {
         domRegistryDependency.registerProvider(salProvider);
         bindingBroker.registerProvider(salProvider);
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/SchemalessNetconfDeviceTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/SchemalessNetconfDeviceTest.java
new file mode 100644 (file)
index 0000000..7dd8064
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.connect.netconf;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.Lists;
+import java.lang.reflect.Field;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceRpc;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class SchemalessNetconfDeviceTest {
+
+    private static final String TEST_NAMESPACE = "test:namespace";
+    private static final String TEST_MODULE = "test-module";
+    private static final String TEST_REVISION = "2013-07-22";
+
+    @Test
+    public void testSessionOnMethods() throws Exception {
+        final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
+        final NetconfDeviceCommunicator listener = mockCloseableClass(NetconfDeviceCommunicator.class);
+        final SchemalessMessageTransformer messageTransformer = mock(SchemalessMessageTransformer.class);
+        final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test-D",
+                InetSocketAddress.createUnresolved("localhost", 22));
+        final Throwable throwable = new Throwable();
+
+        final SchemalessNetconfDevice device = new SchemalessNetconfDevice(remoteDeviceId, facade, messageTransformer);
+
+        final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
+                Lists.newArrayList(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
+
+        final NetconfMessage netconfMessage = mock(NetconfMessage.class);
+
+        device.onRemoteSessionUp(sessionCaps, listener);
+        verify(facade).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(DOMRpcService.class));
+
+        device.onNotification(netconfMessage);
+        verify(facade).onNotification(any(DOMNotification.class));
+
+        device.onRemoteSessionDown();
+        verify(facade).onDeviceDisconnected();
+
+        device.onRemoteSessionFailed(throwable);
+        verify(facade).onDeviceFailed(throwable);
+    }
+
+    @SuppressWarnings("unchecked")
+    private RemoteDeviceHandler<NetconfSessionPreferences> getFacade() throws Exception {
+        final RemoteDeviceHandler<NetconfSessionPreferences> remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class);
+        doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(NetconfDeviceRpc.class));
+        doNothing().when(remoteDeviceHandler).onDeviceDisconnected();
+        doNothing().when(remoteDeviceHandler).onNotification(any(DOMNotification.class));
+        return remoteDeviceHandler;
+    }
+
+    private <T extends AutoCloseable> T mockCloseableClass(final Class<T> remoteDeviceHandlerClass) throws Exception {
+        final T mock = mockClass(remoteDeviceHandlerClass);
+        doNothing().when(mock).close();
+        return mock;
+    }
+
+    private static <T> T mockClass(final Class<T> remoteDeviceHandlerClass) {
+        final T mock = mock(remoteDeviceHandlerClass);
+        Mockito.doReturn(remoteDeviceHandlerClass.getSimpleName()).when(mock).toString();
+        return mock;
+    }
+
+    private NetconfSessionPreferences getSessionCaps(final boolean addMonitor, final Collection<String> additionalCapabilities) {
+        final ArrayList<String> capabilities = Lists.newArrayList(
+                XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0,
+                XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1);
+
+        if(addMonitor) {
+            capabilities.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
+        }
+
+        capabilities.addAll(additionalCapabilities);
+
+        return NetconfSessionPreferences.fromStrings(
+                capabilities);
+    }
+}
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacadeTest.java
new file mode 100644 (file)
index 0000000..07577ca
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.connect.netconf.sal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.controller.sal.core.api.Broker;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
+import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({NetconfDeviceTopologyAdapter.class, NetconfDeviceSalProvider.MountInstance.class, NetconfSessionPreferences.class})
+public class NetconfDeviceSalFacadeTest {
+
+    private NetconfDeviceSalFacade deviceFacade;
+
+    private NetconfDeviceTopologyAdapter netconfDeviceTopologyAdapter;
+    private NetconfDeviceSalProvider.MountInstance mountInstance;
+
+    @Mock
+    private NetconfDeviceSalProvider salProvider;
+
+    @Before
+    public void setUp() throws Exception{
+        initMocks(this);
+        final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
+        final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test", address);
+
+        final Broker domRegistry = mock(Broker.class);
+        final BindingAwareBroker bindingRegistry = mock(BindingAwareBroker.class);
+        deviceFacade = new NetconfDeviceSalFacade(remoteDeviceId, salProvider, domRegistry, bindingRegistry);
+
+        netconfDeviceTopologyAdapter = PowerMockito.mock(NetconfDeviceTopologyAdapter.class);
+        mountInstance = PowerMockito.mock(NetconfDeviceSalProvider.MountInstance.class);
+
+        doReturn(netconfDeviceTopologyAdapter).when(salProvider).getTopologyDatastoreAdapter();
+        doNothing().when(netconfDeviceTopologyAdapter).updateDeviceData(any(Boolean.class), any(NetconfDeviceCapabilities.class));
+
+        doReturn(mountInstance).when(salProvider).getMountInstance();
+        doNothing().when(mountInstance).onTopologyDeviceDisconnected();
+    }
+
+    @Test
+    public void testOnDeviceDisconnected() {
+        deviceFacade.onDeviceDisconnected();
+
+        verify(netconfDeviceTopologyAdapter).updateDeviceData(eq(false), any(NetconfDeviceCapabilities.class));
+        verify(mountInstance, times(1)).onTopologyDeviceDisconnected();
+
+    }
+
+    @Test
+    public void testOnDeviceFailed() {
+        final Throwable throwable = new Throwable();
+        deviceFacade.onDeviceFailed(throwable);
+
+        verify(netconfDeviceTopologyAdapter).setDeviceAsFailed(throwable);
+        verify(mountInstance, times(1)).onTopologyDeviceDisconnected();
+    }
+
+    @Test
+    public void testOnDeviceClose() throws Exception {
+        deviceFacade.close();
+        verify(salProvider).close();
+    }
+
+    @Test
+    public void testOnDeviceConnected() {
+        final SchemaContext schemaContext = mock(SchemaContext.class);
+
+        final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(getCapabilities());
+
+        final DOMRpcService deviceRpc = mock(DOMRpcService.class);
+        deviceFacade.onDeviceConnected(schemaContext, netconfSessionPreferences, deviceRpc);
+
+        verify(mountInstance, times(1)).onTopologyDeviceConnected(eq(schemaContext), any(DOMDataBroker.class), eq(deviceRpc), any(NetconfDeviceNotificationService.class));
+        verify(netconfDeviceTopologyAdapter, times(1)).updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities());
+    }
+
+    @Test
+    public void testOnDeviceNotification() throws Exception {
+        final DOMNotification domNotification = mock(DOMNotification.class);
+        deviceFacade.onNotification(domNotification);
+        verify(mountInstance).publish(domNotification);
+    }
+
+   private List<String> getCapabilities(){
+        return Arrays.asList(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
+    }
+}
index 2358819f90439f8e1a8abf0a2666dd63c0aa89ea..0af8a257e71c726dc26d78253a9ddc6b8a75151c 100644 (file)
@@ -23,28 +23,22 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import javassist.ClassPool;
-import org.custommonkey.xmlunit.XMLUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.internal.util.collections.Sets;
 import org.opendaylight.controller.cluster.databroker.ConcurrentDOMDataBroker;
-import org.opendaylight.controller.cluster.datastore.node.utils.AugmentationIdentifierGenerator;
-import org.opendaylight.controller.md.sal.binding.impl.BindingDOMDataBrokerAdapter;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
-import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.binding.impl.BindingDOMDataBrokerAdapter;
 import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStoreFactory;
@@ -52,15 +46,11 @@ import org.opendaylight.controller.sal.core.api.model.SchemaService;
 import org.opendaylight.controller.sal.core.spi.data.DOMStore;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeFields;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
-import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
 import org.opendaylight.yangtools.binding.data.codec.gen.impl.DataObjectSerializerGenerator;
 import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
 import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
@@ -289,4 +279,16 @@ public class NetconfDeviceTopologyAdapterTest {
         };
     }
 
+    @Test
+    public void testRemoveDeviceConfiguration() throws Exception {
+        doReturn(Futures.immediateCheckedFuture(null)).when(writeTx).submit();
+
+        NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, txChain);
+        adapter.close();
+
+        verify(txChain, times(2)).newWriteOnlyTransaction();
+        verify(writeTx).delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath());
+        verify(writeTx, times(2)).submit();
+    }
+
 }
\ No newline at end of file
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/util/RemoteDeviceIdTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/util/RemoteDeviceIdTest.java
new file mode 100644 (file)
index 0000000..02d3eea
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.connect.netconf.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+
+public class RemoteDeviceIdTest {
+
+    @Test
+    public void testEquals() {
+        final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
+        final ModuleIdentifier identifier = new ModuleIdentifier("test", "test");
+
+        final RemoteDeviceId remoteDeviceId = new RemoteDeviceId("test", address);
+        final RemoteDeviceId remoteDeviceIdEqualName = new RemoteDeviceId(identifier, address);
+        final RemoteDeviceId remoteDeviceIdDiffName = new RemoteDeviceId("test-diff", address);
+
+        assertEquals(true, remoteDeviceId.equals(remoteDeviceId));
+        assertEquals(false, remoteDeviceId.equals(this));
+        assertEquals(false, remoteDeviceId.equals(remoteDeviceIdDiffName));
+        assertEquals(true, remoteDeviceId.equals(remoteDeviceIdEqualName));
+    }
+
+    @Test
+    public void testHashCode() {
+        final String name = "name";
+        final InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
+        final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(name, address);
+        final RemoteDeviceId remoteDeviceIdEqualName = new RemoteDeviceId(name, address);
+        final RemoteDeviceId remoteDeviceIdDiffName = new RemoteDeviceId("test-diff", address);
+
+        assertEquals(remoteDeviceIdEqualName.hashCode(), remoteDeviceId.hashCode());
+        assertNotEquals(remoteDeviceIdDiffName.hashCode(), remoteDeviceId.hashCode());
+    }
+}
index 2252252e110743de3f22f48b9a081cdd1da41e75..8c083399fc28ceac882d4d49ce13a0803fb04898 100644 (file)
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
index 4e7b890e9a0f6a0132923904585fa58b5c6ff43b..f867971aea5051b9ba984025c48e687b2c6fe434 100644 (file)
@@ -15,10 +15,10 @@ import com.google.common.util.concurrent.CheckedFuture;
 import java.io.IOException;
 import org.opendaylight.yanglib.api.YangLibService;
 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
-import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,13 +28,13 @@ import org.slf4j.LoggerFactory;
 public class YangLibServiceImpl implements YangLibService {
     private static final Logger LOG = LoggerFactory.getLogger(YangLibServiceImpl.class);
 
-    private SharedSchemaRepository schemaRepository;
+    private SchemaRepository schemaRepository;
 
     public YangLibServiceImpl() {
 
     }
 
-    public void setSchemaRepository(final SharedSchemaRepository schemaRepository) {
+    public void setSchemaRepository(final SchemaRepository schemaRepository) {
         LOG.debug("Setting schema repository {}", schemaRepository);
         this.schemaRepository = schemaRepository;
     }
diff --git a/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibRestAppTest.java b/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibRestAppTest.java
new file mode 100644 (file)
index 0000000..bba2c29
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.yanglib.impl;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.BDDMockito;
+import org.opendaylight.yanglib.api.YangLibRestAppService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(FrameworkUtil.class)
+public class YangLibRestAppTest {
+
+    @Test
+    public void testYangLibRestApp() {
+        PowerMockito.mockStatic(FrameworkUtil.class);
+
+        final BundleContext bundleContext = mock(BundleContext.class);
+        final Bundle bundle = mock(Bundle.class);
+
+        BDDMockito.given(FrameworkUtil.getBundle(any())).willReturn(bundle);
+        when(bundle.getBundleContext()).thenReturn(bundleContext);
+
+        final YangLibRestApp yangLibRestApp = new YangLibRestApp();
+        final Set singleton = yangLibRestApp.getSingletons();
+
+        assertTrue(singleton.contains(yangLibRestApp.getYangLibService()));
+
+        verify(bundleContext, times(1)).registerService(eq(YangLibRestAppService.class.getName()), eq(yangLibRestApp), eq(null));
+    }
+}
diff --git a/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibServiceImplTest.java b/netconf/yanglib/src/test/java/org/opendaylight/yanglib/impl/YangLibServiceImplTest.java
new file mode 100644 (file)
index 0000000..add8d67
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.yanglib.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+
+public class YangLibServiceImplTest {
+
+    private final static String TEST_OUTPUT_STRING = "hello world";
+
+    @Test
+    public void testSchema() throws SchemaSourceException {
+
+        final SchemaRepository schemaRepository = mock(SchemaRepository.class);
+        final YangLibServiceImpl yangLibService = new YangLibServiceImpl();
+        yangLibService.setSchemaRepository(schemaRepository);
+
+        final SourceIdentifier sourceIdentifier = RevisionSourceIdentifier.create("name", "2016-01-01");
+
+        final YangTextSchemaSource yangTextSchemaSource = new YangTextSchemaSource(sourceIdentifier) {
+            @Override
+            protected MoreObjects.ToStringHelper addToStringAttributes(MoreObjects.ToStringHelper toStringHelper) {
+                return null;
+            }
+
+            @Override
+            public InputStream openStream() throws IOException {
+                return new ByteArrayInputStream(TEST_OUTPUT_STRING.getBytes());
+            }
+        };
+
+        final CheckedFuture<YangTextSchemaSource, SchemaSourceException> sourceFuture =
+                Futures.immediateCheckedFuture(yangTextSchemaSource);
+        doReturn(sourceFuture).when(schemaRepository).getSchemaSource(any(SourceIdentifier.class),
+                eq(YangTextSchemaSource.class));
+
+        final String outputStream = yangLibService.getSchema("name", "2016-01-01");
+        assertEquals(TEST_OUTPUT_STRING, outputStream);
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index 604a91ca0b4bbd61700746164d1bd7e327309a10..b7ff23ef51bf73de607daef39864e5008492229f 100644 (file)
--- a/pom.xml
+++ b/pom.xml
       </dependencies>
     </dependencyManagement>
 
-    <build>
-        <pluginManagement>
-            <plugins>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-checkstyle-plugin</artifactId>
-                    <dependencies>
-                        <dependency>
-                            <groupId>org.opendaylight.controller</groupId>
-                            <artifactId>checkstyle</artifactId>
-                            <version>0.4.0-SNAPSHOT</version>
-                        </dependency>
-                    </dependencies>
-                </plugin>
-            </plugins>
-        </pluginManagement>
-    </build>
-
     <profiles>
         <profile>
             <id>integrationtests</id>
index d3f6017c755c6d435324689eabb3429d1f87bb41..bce2cbfe3b686874f9153ecb9989255e8d8213cf 100644 (file)
       <groupId>org.opendaylight.controller</groupId>
       <artifactId>sal-binding-broker-impl</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.opendaylight.controller</groupId>
-      <artifactId>sal-remote</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.opendaylight.yangtools</groupId>
       <artifactId>yang-data-api</artifactId>
index a265ea95a212b40c6dc4bcd5698fb2de57e5c372..77489dec72694c138b0ed0b7e3e6531aee3f103e 100644 (file)
@@ -12,6 +12,7 @@ import com.google.common.base.Preconditions;
 import java.net.URI;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import org.opendaylight.restconf.utils.patch.Draft16StringModuleInstanceIdentifierCodec;
 import org.opendaylight.yangtools.yang.data.util.AbstractModuleStringInstanceIdentifierCodec;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.Module;
@@ -19,29 +20,29 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 /**
  * @deprecated This class will be replaced by
- * {@link org.opendaylight.restconf.utils.patch.Draft11StringModuleInstanceIdentifierCodec}
+ *             {@link Draft16StringModuleInstanceIdentifierCodec}
  */
 @Deprecated
-final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+public final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
 
     private final DataSchemaContextTree dataContextTree;
     private final SchemaContext context;
     private final String defaultPrefix;
 
-    StringModuleInstanceIdentifierCodec(SchemaContext context) {
+    public StringModuleInstanceIdentifierCodec(final SchemaContext context) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = "";
     }
 
-    StringModuleInstanceIdentifierCodec(SchemaContext context, @Nonnull String defaultPrefix) {
+    StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = defaultPrefix;
     }
 
     @Override
-    protected Module moduleForPrefix(@Nonnull String prefix) {
+    protected Module moduleForPrefix(@Nonnull final String prefix) {
         if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
             return this.context.findModuleByName(this.defaultPrefix, null);
         } else {
@@ -57,7 +58,7 @@ final class StringModuleInstanceIdentifierCodec extends AbstractModuleStringInst
 
     @Nullable
     @Override
-    protected String prefixForNamespace(@Nonnull URI namespace) {
+    protected String prefixForNamespace(@Nonnull final URI namespace) {
         final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
         return module == null ? null : module.getName();
     }
index 4377dafabff73d2884b7880bfea2c1b604311dc6..27f3e78cbc99327338a4a2946a2091042a510cf5 100644 (file)
@@ -132,10 +132,11 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
         } else if (schemaNodeContext instanceof DataSchemaNode) {
             schemaNode = (DataSchemaNode) schemaNodeContext;
         } else {
-            throw new IllegalStateException("Unknow SchemaNode");
+            throw new IllegalStateException("Unknown SchemaNode");
         }
 
         final String docRootElm = doc.getDocumentElement().getLocalName();
+        final String docRootNamespace = doc.getDocumentElement().getNamespaceURI();
         final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
         InstanceIdentifierContext<? extends SchemaNode> outIIContext;
 
@@ -145,7 +146,7 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
                 DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, pathContext.getSchemaContext());
 
         if (isPost() && !isRpc) {
-            final Deque<Object> foundSchemaNodes = findPathToSchemaNodeByName(schemaNode, docRootElm);
+            final Deque<Object> foundSchemaNodes = findPathToSchemaNodeByName(schemaNode, docRootElm, docRootNamespace);
             if (foundSchemaNodes.isEmpty()) {
                 throw new IllegalStateException(String.format("Child \"%s\" was not found in parent schema node \"%s\"",
                         docRootElm, schemaNode.getQName()));
@@ -164,7 +165,7 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
 
         NormalizedNode<?, ?> parsed = null;
 
-        if(schemaNode instanceof ContainerSchemaNode) {
+        if (schemaNode instanceof ContainerSchemaNode) {
             parsed = parserFactory.getContainerNodeParser().parse(Collections.singletonList(doc.getDocumentElement()), (ContainerSchemaNode) schemaNode);
         } else if(schemaNode instanceof ListSchemaNode) {
             final ListSchemaNode casted = (ListSchemaNode) schemaNode;
@@ -184,28 +185,35 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
         return new NormalizedNodeContext(outIIContext, parsed);
     }
 
-    private static Deque<Object> findPathToSchemaNodeByName(final DataSchemaNode schemaNode, final String elementName) {
+    private static Deque<Object> findPathToSchemaNodeByName(final DataSchemaNode schemaNode, final String elementName,
+                                                            final String namespace) {
         final Deque<Object> result = new ArrayDeque<>();
         final ArrayList<ChoiceSchemaNode> choiceSchemaNodes = new ArrayList<>();
         final Collection<DataSchemaNode> children = ((DataNodeContainer) schemaNode).getChildNodes();
         for (final DataSchemaNode child : children) {
             if (child instanceof ChoiceSchemaNode) {
                 choiceSchemaNodes.add((ChoiceSchemaNode) child);
-            } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)) {
+            } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)
+                    && child.getQName().getNamespace().toString().equalsIgnoreCase(namespace)) {
+                // add child to result
                 result.push(child);
+
+                // find augmentation
                 if (child.isAugmenting()) {
                     final AugmentationSchema augment = findCorrespondingAugment(schemaNode, child);
                     if (augment != null) {
                         result.push(augment);
                     }
                 }
+
+                // return result
                 return result;
             }
         }
 
         for (final ChoiceSchemaNode choiceNode : choiceSchemaNodes) {
             for (final ChoiceCaseNode caseNode : choiceNode.getCases()) {
-                final Deque<Object> resultFromRecursion = findPathToSchemaNodeByName(caseNode, elementName);
+                final Deque<Object> resultFromRecursion = findPathToSchemaNodeByName(caseNode, elementName, namespace);
                 if (!resultFromRecursion.isEmpty()) {
                     resultFromRecursion.push(choiceNode);
                     if (choiceNode.isAugmenting()) {
index 2a8d14d98a2d8d6a763068ec37dd96f7cb5f01a6..60347f98f665f286032047bb181164df60755702 100644 (file)
@@ -9,10 +9,10 @@ package org.opendaylight.netconf.sal.restconf.impl;
 
 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
 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.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
@@ -186,94 +186,149 @@ public class BrokerFacade {
         throw new RestconfDocumentedException(errMsg);
     }
 
-    public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
-                                                                      final SchemaContext globalSchema)
-            throws Exception {
-        final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
-        final List<PATCHStatusEntity> editCollection = new ArrayList<>();
+    public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext patchContext) throws Exception {
+        final DOMMountPoint mountPoint = patchContext.getInstanceIdentifierContext().getMountPoint();
 
+        // get new transaction and schema context on server or on mounted device
+        final SchemaContext schemaContext;
+        final DOMDataReadWriteTransaction patchTransaction;
+        if (mountPoint == null) {
+            schemaContext = patchContext.getInstanceIdentifierContext().getSchemaContext();
+            patchTransaction = this.domDataBroker.newReadWriteTransaction();
+        } else {
+            schemaContext = mountPoint.getSchemaContext();
+
+            final Optional<DOMDataBroker> optional = mountPoint.getService(DOMDataBroker.class);
+
+            if (optional.isPresent()) {
+                patchTransaction = optional.get().newReadWriteTransaction();
+            } else {
+                // if mount point does not have broker it is not possible to continue and global error is reported
+                LOG.error("Http PATCH {} has failed - device {} does not support broker service",
+                        patchContext.getPatchId(), mountPoint.getIdentifier());
+                return new PATCHStatusContext(
+                        patchContext.getPatchId(),
+                        null,
+                        false,
+                        ImmutableList.of(new RestconfError(
+                                ErrorType.APPLICATION,
+                                ErrorTag.OPERATION_FAILED,
+                                "DOM data broker service isn't available for mount point "
+                                        + mountPoint.getIdentifier()))
+                );
+            }
+        }
+
+        final List<PATCHStatusEntity> editCollection = new ArrayList<>();
         List<RestconfError> editErrors;
-        int errorCounter = 0;
+        boolean withoutError = true;
 
-        for (final PATCHEntity patchEntity : context.getData()) {
+        for (final PATCHEntity patchEntity : patchContext.getData()) {
             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
 
             switch (operation) {
                 case CREATE:
-                    if (errorCounter == 0) {
+                    if (withoutError) {
                         try {
                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
-                                    patchEntity.getNode(), globalSchema);
+                                    patchEntity.getNode(), schemaContext);
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
                         } catch (final RestconfDocumentedException e) {
+                            LOG.error("Error call http PATCH operation {} on target {}",
+                                    operation,
+                                    patchEntity.getTargetNode().toString());
+
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
-                            errorCounter++;
+                            withoutError = false;
                         }
                     }
                     break;
                 case REPLACE:
-                    if (errorCounter == 0) {
+                    if (withoutError) {
                         try {
                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
-                                    .getTargetNode(), patchEntity.getNode(), globalSchema);
+                                    .getTargetNode(), patchEntity.getNode(), schemaContext);
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
                         } catch (final RestconfDocumentedException e) {
+                            LOG.error("Error call http PATCH operation {} on target {}",
+                                    operation,
+                                    patchEntity.getTargetNode().toString());
+
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
-                            errorCounter++;
+                            withoutError = false;
                         }
                     }
                     break;
                 case DELETE:
-                    if (errorCounter == 0) {
+                    if (withoutError) {
                         try {
                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
                                     .getTargetNode());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
                         } catch (final RestconfDocumentedException e) {
+                            LOG.error("Error call http PATCH operation {} on target {}",
+                                    operation,
+                                    patchEntity.getTargetNode().toString());
+
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
-                            errorCounter++;
+                            withoutError = false;
                         }
                     }
                     break;
                 case REMOVE:
-                    if (errorCounter == 0) {
+                    if (withoutError) {
                         try {
                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
                                     .getTargetNode());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
                         } catch (final RestconfDocumentedException e) {
-                            LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
-                                    patchEntity.getEditId(), e);
+                            LOG.error("Error call http PATCH operation {} on target {}",
+                                    operation,
+                                    patchEntity.getTargetNode().toString());
+
+                            editErrors = new ArrayList<>();
+                            editErrors.addAll(e.getErrors());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+                            withoutError = false;
                         }
                     }
                     break;
                 case MERGE:
-                    if (errorCounter == 0) {
+                    if (withoutError) {
                         try {
                             mergeDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
-                                    patchEntity.getNode(), globalSchema);
+                                    patchEntity.getNode(), schemaContext);
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
                         } catch (final RestconfDocumentedException e) {
+                            LOG.error("Error call http PATCH operation {} on target {}",
+                                    operation,
+                                    patchEntity.getTargetNode().toString());
+
                             editErrors = new ArrayList<>();
                             editErrors.addAll(e.getErrors());
                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
-                            errorCounter++;
+                            withoutError = false;
                         }
                     }
                     break;
+                default:
+                    LOG.error("Unsupported http PATCH operation {} on target {}",
+                            operation,
+                            patchEntity.getTargetNode().toString());
+                    break;
             }
         }
 
         // if errors then cancel transaction and return error status
-        if (errorCounter != 0) {
+        if (!withoutError) {
             patchTransaction.cancel();
-            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
+            return new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection), false, null);
         }
 
         // if no errors commit transaction
@@ -284,7 +339,7 @@ public class BrokerFacade {
         Futures.addCallback(future, new FutureCallback<Void>() {
             @Override
             public void onSuccess(@Nullable final Void result) {
-                status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
+                status.setStatus(new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
                         true, null));
                 waiter.countDown();
             }
@@ -292,8 +347,9 @@ public class BrokerFacade {
             @Override
             public void onFailure(final Throwable t) {
                 // if commit failed it is global error
-                status.setStatus(new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection),
-                        false, Lists.newArrayList(
+                LOG.error("Http PATCH {} transaction commit has failed", patchContext.getPatchId());
+                status.setStatus(new PATCHStatusContext(patchContext.getPatchId(), ImmutableList.copyOf(editCollection),
+                        false, ImmutableList.of(
                         new RestconfError(ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, t.getMessage()))));
                 waiter.countDown();
             }
@@ -397,46 +453,43 @@ public class BrokerFacade {
         return readData.getResult();
     }
 
+    /**
+     * POST data and submit transaction {@link DOMDataReadWriteTransaction}
+     * @return
+     */
     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
             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 {} 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);
-            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);
-        }
+        LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
+        postData(rWTransaction, datastore, path, payload, schemaContext);
         return rWTransaction.submit();
     }
 
+    /**
+     * POST data and do NOT submit transaction {@link DOMDataReadWriteTransaction}
+     */
     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);
+        LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+        postData(rWTransaction, datastore, path, payload, schemaContext);
+    }
+
+    // FIXME: This is doing correct post for container and list children, not sure if this will work for choice case
+    private void postData(final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
+                          final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload,
+                          final SchemaContext schemaContext) {
+        if (payload instanceof MapNode) {
             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()) {
+            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);
+            checkItemDoesNotExists(rWTransaction, datastore, path);
             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
             rWTransaction.put(datastore, path, payload);
         }
@@ -528,21 +581,42 @@ public class BrokerFacade {
         }
     }
 
+    /**
+     * PUT data and submit {@link DOMDataReadWriteTransaction}
+     */
     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
-            final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+            final DOMDataReadWriteTransaction readWriteTransaction, final LogicalDatastoreType datastore,
             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
-        ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
-        writeTransaction.put(datastore, path, payload);
-        return writeTransaction.submit();
+        putData(readWriteTransaction, datastore, path, payload, schemaContext);
+        return readWriteTransaction.submit();
     }
 
+    /**
+     * PUT data and do NOT submit {@link DOMDataReadWriteTransaction}
+     */
     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);
+        putData(writeTransaction, datastore, path, payload, schemaContext);
+    }
+
+    // FIXME: This is doing correct put for container and list children, not sure if this will work for choice case
+    private void putData(final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+            final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+        if (payload instanceof MapNode) {
+            final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+            writeTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+            ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+            for (final MapEntryNode child : ((MapNode) payload).getValue()) {
+                final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+                writeTransaction.put(datastore, childPath, child);
+            }
+        } else {
+            ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+            writeTransaction.put(datastore, path, payload);
+        }
     }
 
     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
index 77e83e367dc71afbfdb443574fabb89fd85f7bf6..c6224663efacbb3e654223c0f016d3161713cbbc 100644 (file)
@@ -14,6 +14,7 @@ import java.util.List;
 import java.util.Map;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.netconf.sal.rest.impl.RestUtil;
+import org.opendaylight.netconf.sal.rest.impl.StringModuleInstanceIdentifierCodec;
 import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.IdentityValue;
 import org.opendaylight.netconf.sal.restconf.impl.IdentityValuesDTO.Predicate;
 import org.opendaylight.yangtools.concepts.Codec;
@@ -63,16 +64,16 @@ public class RestCodec {
         private final TypeDefinition<?> type;
 
         private ObjectCodec(final TypeDefinition<?> typeDefinition, final DOMMountPoint mountPoint) {
-            type = RestUtil.resolveBaseTypeFrom(typeDefinition);
-            if (type instanceof IdentityrefTypeDefinition) {
-                identityrefCodec = new IdentityrefCodecImpl(mountPoint);
+            this.type = RestUtil.resolveBaseTypeFrom(typeDefinition);
+            if (this.type instanceof IdentityrefTypeDefinition) {
+                this.identityrefCodec = new IdentityrefCodecImpl(mountPoint);
             } else {
-                identityrefCodec = null;
+                this.identityrefCodec = null;
             }
-            if (type instanceof InstanceIdentifierTypeDefinition) {
-                instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint);
+            if (this.type instanceof InstanceIdentifierTypeDefinition) {
+                this.instanceIdentifier = new InstanceIdentifierCodecImpl(mountPoint);
             } else {
-                instanceIdentifier = null;
+                this.instanceIdentifier = null;
             }
         }
 
@@ -80,9 +81,9 @@ public class RestCodec {
         @Override
         public Object deserialize(final Object input) {
             try {
-                if (type instanceof IdentityrefTypeDefinition) {
+                if (this.type instanceof IdentityrefTypeDefinition) {
                     if (input instanceof IdentityValuesDTO) {
-                        return identityrefCodec.deserialize(input);
+                        return this.identityrefCodec.deserialize(input);
                     }
                     if(LOG.isDebugEnabled()) {
                         LOG.debug(
@@ -90,24 +91,24 @@ public class RestCodec {
                             input == null ? "null" : input.getClass(), String.valueOf(input));
                     }
                     return null;
-                } else if (type instanceof InstanceIdentifierTypeDefinition) {
+                } else if (this.type instanceof InstanceIdentifierTypeDefinition) {
                     if (input instanceof IdentityValuesDTO) {
-                        return instanceIdentifier.deserialize(input);
+                        return this.instanceIdentifier.deserialize(input);
+                    } else {
+                        final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec(
+                                ControllerContext.getInstance().getGlobalSchema());
+                        return codec.deserialize((String) input);
                     }
-                    LOG.info(
-                            "Value is not instance of InstanceIdentifierTypeDefinition but is {}. Therefore NULL is used as translation of  - {}",
-                            input == null ? "null" : input.getClass(), String.valueOf(input));
-                    return null;
                 } else {
                     final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec = TypeDefinitionAwareCodec
-                            .from(type);
+                            .from(this.type);
                     if (typeAwarecodec != null) {
                         if (input instanceof IdentityValuesDTO) {
                             return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue());
                         }
                         return typeAwarecodec.deserialize(String.valueOf(input));
                     } else {
-                        LOG.debug("Codec for type \"" + type.getQName().getLocalName()
+                        LOG.debug("Codec for type \"" + this.type.getQName().getLocalName()
                                 + "\" is not implemented yet.");
                         return null;
                     }
@@ -124,20 +125,20 @@ public class RestCodec {
         @Override
         public Object serialize(final Object input) {
             try {
-                if (type instanceof IdentityrefTypeDefinition) {
-                    return identityrefCodec.serialize(input);
-                } else if (type instanceof LeafrefTypeDefinition) {
+                if (this.type instanceof IdentityrefTypeDefinition) {
+                    return this.identityrefCodec.serialize(input);
+                } else if (this.type instanceof LeafrefTypeDefinition) {
                     return LEAFREF_DEFAULT_CODEC.serialize(input);
-                } else if (type instanceof InstanceIdentifierTypeDefinition) {
-                    return instanceIdentifier.serialize(input);
+                } else if (this.type instanceof InstanceIdentifierTypeDefinition) {
+                    return this.instanceIdentifier.serialize(input);
                 } else {
                     final TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>> typeAwarecodec = TypeDefinitionAwareCodec
-                            .from(type);
+                            .from(this.type);
                     if (typeAwarecodec != null) {
                         return typeAwarecodec.serialize(input);
                     } else {
                         if(LOG.isDebugEnabled()) {
-                            LOG.debug("Codec for type \"" + type.getQName().getLocalName()
+                            LOG.debug("Codec for type \"" + this.type.getQName().getLocalName()
                                 + "\" is not implemented yet.");
                         }
                         return null;
@@ -171,7 +172,7 @@ public class RestCodec {
         @Override
         public QName deserialize(final IdentityValuesDTO data) {
             final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
-            final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint);
+            final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), this.mountPoint);
             if (module == null) {
                 LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace());
                 LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace));
@@ -210,11 +211,11 @@ public class RestCodec {
             final IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO();
             for (final PathArgument pathArgument : data.getPathArguments()) {
                 final IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType());
-                if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) {
+                if ((pathArgument instanceof NodeIdentifierWithPredicates) && (identityValue != null)) {
                     final List<Predicate> predicates = keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument)
                             .getKeyValues());
                     identityValue.setPredicates(predicates);
-                } else if (pathArgument instanceof NodeWithValue && identityValue != null) {
+                } else if ((pathArgument instanceof NodeWithValue) && (identityValue != null)) {
                     final List<Predicate> predicates = new ArrayList<>();
                     final String value = String.valueOf(((NodeWithValue) pathArgument).getValue());
                     predicates.add(new Predicate(null, value));
@@ -229,7 +230,7 @@ public class RestCodec {
         public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) {
             final List<PathArgument> result = new ArrayList<PathArgument>();
             final IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0);
-            final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), mountPoint);
+            final Module module = getModuleByNamespace(valueWithNamespace.getNamespace(), this.mountPoint);
             if (module == null) {
                 LOG.info("Module by namespace '{}' of first node in instance-identifier was not found.",
                         valueWithNamespace.getNamespace());
@@ -242,7 +243,7 @@ public class RestCodec {
             final List<IdentityValue> identities = data.getValuesWithNamespaces();
             for (int i = 0; i < identities.size(); i++) {
                 final IdentityValue identityValue = identities.get(i);
-                URI validNamespace = resolveValidNamespace(identityValue.getNamespace(), mountPoint);
+                URI validNamespace = resolveValidNamespace(identityValue.getNamespace(), this.mountPoint);
                 final DataSchemaNode node = ControllerContext.findInstanceDataChildByNameAndNamespace(
                         parentContainer, identityValue.getValue(), validNamespace);
                 if (node == null) {
@@ -269,7 +270,7 @@ public class RestCodec {
                         final DataNodeContainer listNode = (DataNodeContainer) node;
                         final Map<QName, Object> predicatesMap = new HashMap<>();
                         for (final Predicate predicate : identityValue.getPredicates()) {
-                            validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), mountPoint);
+                            validNamespace = resolveValidNamespace(predicate.getName().getNamespace(), this.mountPoint);
                             final DataSchemaNode listKey = ControllerContext
                                     .findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(),
                                             validNamespace);
@@ -284,7 +285,7 @@ public class RestCodec {
                     }
                 }
                 result.add(pathArgument);
-                if (i < identities.size() - 1) { // last element in instance-identifier can be other than
+                if (i < (identities.size() - 1)) { // last element in instance-identifier can be other than
                     // DataNodeContainer
                     if (node instanceof DataNodeContainer) {
                         parentContainer = (DataNodeContainer) node;
index 005f659993ee6f6f07e82cdc000f1a6ad2c61250..669028f38e7142dc668f2516015dc895df0f6a93 100644 (file)
@@ -1122,8 +1122,7 @@ public class RestconfImpl implements RestconfService {
         }
 
         try {
-            return this.broker.patchConfigurationDataWithinTransaction(context,
-                    this.controllerContext.getGlobalSchema());
+            return this.broker.patchConfigurationDataWithinTransaction(context);
         } catch (final Exception e) {
             LOG.debug("Patch transaction failed", e);
             throw new RestconfDocumentedException(e.getMessage());
@@ -1137,8 +1136,7 @@ public class RestconfImpl implements RestconfService {
         }
 
         try {
-            return this.broker.patchConfigurationDataWithinTransaction(context,
-                    this.controllerContext.getGlobalSchema());
+            return this.broker.patchConfigurationDataWithinTransaction(context);
         } catch (final Exception e) {
             LOG.debug("Patch transaction failed", e);
             throw new RestconfDocumentedException(e.getMessage());
index 7890ee035b70b7adc4e859af0a8100a6a39413e3..ded3543a88f3293c7c82dc4ea75de87b040d9cb1 100644 (file)
@@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
 public final class PatchDataTransactionUtil {
     private static final Logger LOG = LoggerFactory.getLogger(PatchDataTransactionUtil.class);
 
-    public PatchDataTransactionUtil() {
+    private PatchDataTransactionUtil() {
         throw new UnsupportedOperationException("Util class.");
     }
 
index ffe9565f4c7fe5c195791c8fe9cbc2bba7f10902..148834fcff50fdd37bef6fa5816d5c23183fc554 100644 (file)
@@ -17,26 +17,26 @@ import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-final class Draft16StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
+public final class Draft16StringModuleInstanceIdentifierCodec extends AbstractModuleStringInstanceIdentifierCodec {
 
     private final DataSchemaContextTree dataContextTree;
     private final SchemaContext context;
     private final String defaultPrefix;
 
-    Draft16StringModuleInstanceIdentifierCodec(SchemaContext context) {
+    public Draft16StringModuleInstanceIdentifierCodec(final SchemaContext context) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = "";
     }
 
-    Draft16StringModuleInstanceIdentifierCodec(SchemaContext context, @Nonnull String defaultPrefix) {
+    Draft16StringModuleInstanceIdentifierCodec(final SchemaContext context, @Nonnull final String defaultPrefix) {
         this.context = Preconditions.checkNotNull(context);
         this.dataContextTree = DataSchemaContextTree.from(context);
         this.defaultPrefix = defaultPrefix;
     }
 
     @Override
-    protected Module moduleForPrefix(@Nonnull String prefix) {
+    protected Module moduleForPrefix(@Nonnull final String prefix) {
         if (prefix.isEmpty() && !this.defaultPrefix.isEmpty()) {
             return this.context.findModuleByName(this.defaultPrefix, null);
         } else {
@@ -52,7 +52,7 @@ final class Draft16StringModuleInstanceIdentifierCodec extends AbstractModuleStr
 
     @Nullable
     @Override
-    protected String prefixForNamespace(@Nonnull URI namespace) {
+    protected String prefixForNamespace(@Nonnull final URI namespace) {
         final Module module = this.context.findModuleByNamespaceAndRevision(namespace, null);
         return module == null ? null : module.getName();
     }
diff --git a/restconf/sal-rest-connector/src/main/yang/opendaylight-md-sal-remote.yang b/restconf/sal-rest-connector/src/main/yang/opendaylight-md-sal-remote.yang
new file mode 100644 (file)
index 0000000..994e070
--- /dev/null
@@ -0,0 +1,97 @@
+module sal-remote {
+
+    yang-version 1;
+    namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote";
+    prefix "sal-remote";
+
+    organization "Cisco Systems, Inc.";
+    contact "Martin Bobak <mbobak@cisco.com>";
+
+    description
+          "This module contains the definition of methods related to
+           sal remote model.
+
+           Copyright (c)2013 Cisco Systems, Inc. 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";
+
+    revision "2014-01-14" {
+        description
+            "Initial revision";
+    }
+
+
+     typedef q-name {
+       type string;
+       reference
+         "http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#QName";
+     }
+
+    rpc create-data-change-event-subscription {
+        input {
+            leaf path {
+                type instance-identifier;
+                description "Subtree path. ";
+            }
+         }
+         output {
+            leaf stream-name {
+                type string;
+                description "Notification stream name.";
+            }
+         }
+    }
+
+    notification data-changed-notification {
+        description "Data change notification.";
+        list data-change-event {
+            key path;
+            leaf path {
+                type instance-identifier;
+            }
+            leaf store {
+                type enumeration {
+                    enum config;
+                    enum operation;
+                }
+            }
+            leaf operation {
+                type enumeration {
+                    enum created;
+                    enum updated;
+                    enum deleted;
+                }
+            }
+            anyxml data{
+                description "DataObject ";
+            }
+         }
+    }
+
+    rpc create-notification-stream {
+        input {
+            leaf-list notifications {
+                type q-name;
+                description "Notification QNames";
+            }
+         }
+        output {
+            leaf notification-stream-identifier {
+                type string;
+                description "Unique notification stream identifier, in which notifications will be propagated";
+            }
+        }
+    }
+
+    rpc begin-transaction {
+        output {
+            anyxml data-modification-transaction{
+                description "DataModificationTransaction xml";
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/InstanceIdentifierTypeLeafTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/InstanceIdentifierTypeLeafTest.java
new file mode 100644 (file)
index 0000000..8439d0c
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.controller.sal.rest.impl;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class InstanceIdentifierTypeLeafTest {
+
+    @Test
+    public void stringToInstanceIdentifierTest() throws Exception {
+        final SchemaContext schemaContext = TestRestconfUtils.loadSchemaContext("/instanceidentifier");
+        ControllerContext.getInstance().setGlobalSchema(schemaContext);
+        final InstanceIdentifierContext<?> instanceIdentifier =
+                ControllerContext.getInstance().toInstanceIdentifier(
+                        "/iid-value-module:cont-iid/iid-list/%2Fiid-value-module%3Acont-iid%2Fiid-value-module%3A"
+                                + "values-iid%5Biid-value-module:value-iid='value'%5D");
+        final YangInstanceIdentifier yiD = instanceIdentifier.getInstanceIdentifier();
+        Assert.assertNotNull(yiD);
+        final PathArgument lastPathArgument = yiD.getLastPathArgument();
+        Assert.assertTrue(lastPathArgument.getNodeType().getNamespace().toString().equals("iid:value:module"));
+        Assert.assertTrue(lastPathArgument.getNodeType().getLocalName().equals("iid-list"));
+
+        final NodeIdentifierWithPredicates list = (NodeIdentifierWithPredicates) lastPathArgument;
+        final YangInstanceIdentifier value = (YangInstanceIdentifier) list.getKeyValues()
+                .get(QName.create(lastPathArgument.getNodeType(), "iid-leaf"));
+        final PathArgument lastPathArgumentOfValue = value.getLastPathArgument();
+        Assert.assertTrue(lastPathArgumentOfValue.getNodeType().getNamespace().toString().equals("iid:value:module"));
+        Assert.assertTrue(lastPathArgumentOfValue.getNodeType().getLocalName().equals("values-iid"));
+
+        final NodeIdentifierWithPredicates valueList = (NodeIdentifierWithPredicates) lastPathArgumentOfValue;
+        final String valueIid = (String) valueList.getKeyValues()
+                .get(QName.create(lastPathArgumentOfValue.getNodeType(), "value-iid"));
+        Assert.assertEquals("value", valueIid);
+    }
+
+}
index ded5e3dc09d477d998da43b9baf1687c84d6e43f..5184d38e58d82c076d3dc531dae52c882bbf3e37 100644 (file)
@@ -100,4 +100,10 @@ public abstract class AbstractBodyReaderTest {
         assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaContext());
         assertNotNull(patchContext.getInstanceIdentifierContext().getSchemaNode());
     }
+
+    protected static void checkPATCHContextMountPoint(final PATCHContext patchContext) {
+        checkPATCHContext(patchContext);
+        assertNotNull(patchContext.getInstanceIdentifierContext().getMountPoint());
+        assertNotNull(patchContext.getInstanceIdentifierContext().getMountPoint().getSchemaContext());
+    }
 }
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReaderMountPoint.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestJsonPATCHBodyReaderMountPoint.java
new file mode 100644 (file)
index 0000000..74058ca
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.netconf.sal.rest.impl.JsonToPATCHBodyReader;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestJsonPATCHBodyReaderMountPoint extends AbstractBodyReaderTest {
+
+    private final JsonToPATCHBodyReader jsonPATCHBodyReader;
+    private static SchemaContext schemaContext;
+    private static final String MOUNT_POINT = "instance-identifier-module:cont/yang-ext:mount";
+
+    public TestJsonPATCHBodyReaderMountPoint() 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);
+
+        final DOMMountPoint mockMountPoint = mock(DOMMountPoint.class);
+        when(mockMountPoint.getSchemaContext()).thenReturn(schemaContext);
+        final DOMMountPointService mockMountPointService = mock(DOMMountPointService.class);
+        when(mockMountPointService.getMountPoint(any(YangInstanceIdentifier.class)))
+                .thenReturn(Optional.of(mockMountPoint));
+
+        controllerContext.setMountService(mockMountPointService);
+        controllerContext.setSchemas(schemaContext);
+    }
+
+    @Test
+    public void modulePATCHDataTest() throws Exception {
+        final String uri = MOUNT_POINT + "/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);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test of successful PATCH consisting of create and delete PATCH operations.
+     */
+    @Test
+    public void modulePATCHCreateAndDeleteTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCreateAndDelete.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test trying to use PATCH create operation which requires value without value. Test should fail with
+     * {@link RestconfDocumentedException} with error code 400.
+     */
+    @Test
+    public void modulePATCHValueMissingNegativeTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueMissing.json");
+
+        try {
+            jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test trying to use value with PATCH delete operation which does not support value. Test should fail with
+     * {@link RestconfDocumentedException} with error code 400.
+     */
+    @Test
+    public void modulePATCHValueNotSupportedNegativeTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
+
+        try {
+            jsonPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+     */
+    @Test
+    public void modulePATCHCompleteTargetInURITest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+     */
+    @Test
+    public void modulePATCHMergeOperationOnListTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnList.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+     */
+    @Test
+    public void modulePATCHMergeOperationOnContainerTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, jsonPATCHBodyReader, false);
+
+        final InputStream inputStream = TestJsonBodyReader.class
+                .getResourceAsStream("/instanceidentifier/json/jsonPATCHMergeOperationOnContainer.json");
+
+        final PATCHContext returnValue = jsonPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+}
index 50ce26a7f4d932ad943f1e590e94aa9559f14c2d..e14246434c06db91f3d6e4d510f478e042762e7a 100644 (file)
@@ -209,4 +209,48 @@ public class TestXmlBodyReader extends AbstractBodyReaderTest {
         assertEquals(dataNodeIdent, nnContext.getInstanceIdentifierContext().getInstanceIdentifier());
         assertNotNull(NormalizedNodes.findNode(nnContext.getData(), dataNodeIdent));
     }
+
+    /**
+     * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+     * used to distinguish between them to find correct one. Check if container was found not only according to its name
+     * but also by correct namespace used in payload.
+     */
+    @Test
+    public void findFooContainerUsingNamespaceTest() throws Exception {
+        mockBodyReader("", xmlBodyReader, true);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml");
+        final NormalizedNodeContext returnValue = xmlBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+
+        // check return value
+        checkNormalizedNodeContext(returnValue);
+        // check if container was found both according to its name and namespace
+        assertEquals("Not correct container found, name was ignored",
+                "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+        assertEquals("Not correct container found, namespace was ignored",
+                "foo:module", returnValue.getData().getNodeType().getNamespace().toString());
+    }
+
+    /**
+     * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+     * used to distinguish between them to find correct one. Check if container was found not only according to its name
+     * but also by correct namespace used in payload.
+     */
+    @Test
+    public void findBarContainerUsingNamespaceTest() throws Exception {
+        mockBodyReader("", xmlBodyReader, true);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml");
+        final NormalizedNodeContext returnValue = xmlBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+
+        // check return value
+        checkNormalizedNodeContext(returnValue);
+        // check if container was found both according to its name and namespace
+        assertEquals("Not correct container found, name was ignored",
+                "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+        assertEquals("Not correct container found, namespace was ignored",
+                "bar:module", returnValue.getData().getNodeType().getNamespace().toString());
+    }
 }
index 8376c76b9b07ad72bf4979317cb8bdea968edd71..2bb8ece0e10fa8651e1ae52ec66dd4c38c9015f0 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.controller.sal.rest.impl.test.providers;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -189,4 +190,48 @@ public class TestXmlBodyReaderMountPoint extends AbstractBodyReaderTest {
         assertNotNull(NormalizedNodes.findNode(nnContext.getData(),
                 dataNodeIdent));
     }
+
+    /**
+     * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+     * used to distinguish between them to find correct one. Check if container was found not only according to its name
+     * but also by correct namespace used in payload.
+     */
+    @Test
+    public void findFooContainerUsingNamespaceTest() throws Exception {
+        mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, true);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml");
+        final NormalizedNodeContext returnValue = xmlBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+
+        // check return value
+        checkMountPointNormalizedNodeContext(returnValue);
+        // check if container was found both according to its name and namespace
+        assertEquals("Not correct container found, name was ignored",
+                "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+        assertEquals("Not correct container found, namespace was ignored",
+                "foo:module", returnValue.getData().getNodeType().getNamespace().toString());
+    }
+
+    /**
+     * Test when container with the same name is placed in two modules (foo-module and bar-module). Namespace must be
+     * used to distinguish between them to find correct one. Check if container was found not only according to its name
+     * but also by correct namespace used in payload.
+     */
+    @Test
+    public void findBarContainerUsingNamespaceTest() throws Exception {
+        mockBodyReader("instance-identifier-module:cont/yang-ext:mount", xmlBodyReader, true);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml");
+        final NormalizedNodeContext returnValue = xmlBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+
+        // check return value
+        checkMountPointNormalizedNodeContext(returnValue);
+        // check if container was found both according to its name and namespace
+        assertEquals("Not correct container found, name was ignored",
+                "foo-bar-container", returnValue.getData().getNodeType().getLocalName());
+        assertEquals("Not correct container found, namespace was ignored",
+                "bar:module", returnValue.getData().getNodeType().getNamespace().toString());
+    }
 }
index 4768a0c9e8a5375b4568850a2181d2d65a920c61..a6d9352f4a8d8f72a55e5c607f367597f29f969a 100644 (file)
@@ -87,7 +87,6 @@ public class TestXmlPATCHBodyReader extends AbstractBodyReaderTest {
         }
     }
 
-
     /**
      * Test of Yang PATCH with absolute target path.
      */
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReaderMountPoint.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/rest/impl/test/providers/TestXmlPATCHBodyReaderMountPoint.java
new file mode 100644 (file)
index 0000000..3cf253a
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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.controller.sal.rest.impl.test.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import java.io.InputStream;
+import javax.ws.rs.core.MediaType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.netconf.sal.rest.impl.XmlToPATCHBodyReader;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+public class TestXmlPATCHBodyReaderMountPoint extends AbstractBodyReaderTest {
+
+    private final XmlToPATCHBodyReader xmlPATCHBodyReader;
+    private static SchemaContext schemaContext;
+    private static final String MOUNT_POINT = "instance-identifier-module:cont/yang-ext:mount";
+
+    public TestXmlPATCHBodyReaderMountPoint() 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);
+
+        final DOMMountPoint mockMountPoint = mock(DOMMountPoint.class);
+        when(mockMountPoint.getSchemaContext()).thenReturn(schemaContext);
+        final DOMMountPointService mockMountPointService = mock(DOMMountPointService.class);
+        when(mockMountPointService.getMountPoint(any(YangInstanceIdentifier.class)))
+                .thenReturn(Optional.of(mockMountPoint));
+
+        controllerContext.setMountService(mockMountPointService);
+        controllerContext.setSchemas(schemaContext);
+    }
+
+    @Test
+    public void moduleDataTest() throws Exception {
+        final String uri = MOUNT_POINT + "/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);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test trying to use PATCH create operation which requires value without value. Error code 400 should be returned.
+     */
+    @Test
+    public void moduleDataValueMissingNegativeTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
+        try {
+            xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to missing value node when attempt to invoke create operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test trying to use value with PATCH delete operation which does not support value. Error code 400 should be
+     * returned.
+     */
+    @Test
+    public void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
+        try {
+            xmlPATCHBodyReader.readFrom(null, null, null, mediaType, null, inputStream);
+            fail("Test should return error 400 due to present value node when attempt to invoke delete operation");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error code 400 expected", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Test of Yang PATCH with absolute target path.
+     */
+    @Test
+    public void moduleDataAbsoluteTargetPathTest() throws Exception {
+        final String uri = MOUNT_POINT;
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test using PATCH when target is completely specified in request URI and thus target leaf contains only '/' sign.
+     */
+    @Test
+    public void modulePATCHCompleteTargetInURITest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on list. Test consists of two edit operations - replace and merge.
+     */
+    @Test
+    public void moduleDataMergeOperationOnListTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont/my-list1/leaf1";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+
+    /**
+     * Test of Yang PATCH merge operation on container. Test consists of two edit operations - create and merge.
+     */
+    @Test
+    public void moduleDataMergeOperationOnContainerTest() throws Exception {
+        final String uri = MOUNT_POINT + "/instance-identifier-patch-module:patch-cont";
+        mockBodyReader(uri, xmlPATCHBodyReader, false);
+        final InputStream inputStream = TestXmlBodyReader.class
+                .getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml");
+        final PATCHContext returnValue = xmlPATCHBodyReader
+                .readFrom(null, null, null, mediaType, null, inputStream);
+        checkPATCHContextMountPoint(returnValue);
+    }
+}
index 670377069872328e06e85e80001698b9a2a51246..ed3aba5fab41bf5bd0364e9cfc597e26243ef5e1 100644 (file)
@@ -9,8 +9,10 @@
 package org.opendaylight.controller.sal.restconf.impl.test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
@@ -48,6 +50,9 @@ import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
 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.PATCHStatusContext;
 import org.opendaylight.netconf.sal.restconf.impl.PutResult;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
@@ -71,11 +76,23 @@ import org.opendaylight.yangtools.yang.model.api.SchemaPath;
  * @author Thomas Pantelis
  */
 public class BrokerFacadeTest {
-    @Mock private DOMDataBroker domDataBroker;
-    @Mock private DOMNotificationService domNotification;
-    @Mock private ConsumerSession context;
-    @Mock private DOMRpcService mockRpcService;
-    @Mock private DOMMountPoint mockMountInstance;
+
+    @Mock
+    private DOMDataBroker domDataBroker;
+    @Mock
+    private DOMNotificationService domNotification;
+    @Mock
+    private ConsumerSession context;
+    @Mock
+    private DOMRpcService mockRpcService;
+    @Mock
+    private DOMMountPoint mockMountInstance;
+    @Mock
+    private DOMDataReadOnlyTransaction rTransaction;
+    @Mock
+    private DOMDataWriteTransaction wTransaction;
+    @Mock
+    private DOMDataReadWriteTransaction rwTransaction;
 
     private final BrokerFacade brokerFacade = BrokerFacade.getInstance();
     private final NormalizedNode<?, ?> dummyNode = createDummyNode("test:module", "2014-01-09", "interfaces");
@@ -85,10 +102,6 @@ public class BrokerFacadeTest {
     private final SchemaPath type = SchemaPath.create(true, qname);
     private final YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(qname).build();
 
-    @Mock private DOMDataReadOnlyTransaction rTransaction;
-    @Mock private DOMDataWriteTransaction wTransaction;
-    @Mock private DOMDataReadWriteTransaction rwTransaction;
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -339,4 +352,91 @@ public class BrokerFacadeTest {
         listener.close();
         Notificator.removeNotificationListenerIfNoSubscriberExists(listener);
     }
+
+    /**
+     * Test PATCH method on the server with no data
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPatchConfigurationDataWithinTransactionServer() throws Exception {
+        final PATCHContext patchContext = mock(PATCHContext.class);
+        final InstanceIdentifierContext identifierContext = mock(InstanceIdentifierContext.class);
+        final CheckedFuture<Void, TransactionCommitFailedException> expFuture = Futures.immediateCheckedFuture(null);
+
+        when(patchContext.getData()).thenReturn(Lists.newArrayList());
+        when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+
+        // no mount point
+        when(identifierContext.getMountPoint()).thenReturn(null);
+
+        when(this.rwTransaction.submit()).thenReturn(expFuture);
+
+        final PATCHStatusContext status = this.brokerFacade.patchConfigurationDataWithinTransaction(patchContext);
+
+        // assert success
+        assertTrue("PATCH operation should be successful on server", status.isOk());
+    }
+
+    /**
+     * Test PATCH method on mounted device with no data
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPatchConfigurationDataWithinTransactionMount() throws Exception {
+        final PATCHContext patchContext = mock(PATCHContext.class);
+        final InstanceIdentifierContext identifierContext = mock(InstanceIdentifierContext.class);
+        final DOMMountPoint mountPoint = mock(DOMMountPoint.class);
+        final DOMDataBroker mountDataBroker = mock(DOMDataBroker.class);
+        final DOMDataReadWriteTransaction transaction = mock(DOMDataReadWriteTransaction.class);
+        final CheckedFuture<Void, TransactionCommitFailedException> expFuture = Futures.immediateCheckedFuture(null);
+
+        when(patchContext.getData()).thenReturn(Lists.newArrayList());
+        when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+
+        // return mount point with broker
+        when(identifierContext.getMountPoint()).thenReturn(mountPoint);
+        when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.of(mountDataBroker));
+        when(mountDataBroker.newReadWriteTransaction()).thenReturn(transaction);
+        when(transaction.submit()).thenReturn(expFuture);
+
+        final PATCHStatusContext status = this.brokerFacade.patchConfigurationDataWithinTransaction(patchContext);
+
+        // assert success
+        assertTrue("PATCH operation should be successful on mounted device", status.isOk());
+    }
+
+    /**
+     * Negative test for PATCH operation when mounted device does not support {@link DOMDataBroker service.
+     * PATCH operation should fail with global error.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testPatchConfigurationDataWithinTransactionMountFail() throws Exception {
+        final PATCHContext patchContext = mock(PATCHContext.class);
+        final InstanceIdentifierContext identifierContext = mock(InstanceIdentifierContext.class);
+        final DOMMountPoint mountPoint = mock(DOMMountPoint.class);
+        final DOMDataBroker mountDataBroker = mock(DOMDataBroker.class);
+        final DOMDataReadWriteTransaction transaction = mock(DOMDataReadWriteTransaction.class);
+        final CheckedFuture<Void, TransactionCommitFailedException> expFuture = Futures.immediateCheckedFuture(null);
+
+        when(patchContext.getData()).thenReturn(Lists.newArrayList());
+        when(patchContext.getInstanceIdentifierContext()).thenReturn(identifierContext);
+        when(identifierContext.getMountPoint()).thenReturn(mountPoint);
+
+        // missing broker on mounted device
+        when(mountPoint.getService(DOMDataBroker.class)).thenReturn(Optional.absent());
+
+        when(mountDataBroker.newReadWriteTransaction()).thenReturn(transaction);
+        when(transaction.submit()).thenReturn(expFuture);
+
+        final PATCHStatusContext status = this.brokerFacade.patchConfigurationDataWithinTransaction(patchContext);
+
+        // assert not successful operation with error
+        assertNotNull(status.getGlobalErrors());
+        assertEquals(1, status.getGlobalErrors().size());
+        assertEquals(ErrorType.APPLICATION, status.getGlobalErrors().get(0).getErrorType());
+        assertEquals(ErrorTag.OPERATION_FAILED, status.getGlobalErrors().get(0).getErrorTag());
+
+        assertFalse("PATCH operation should fail on mounted device without Broker", status.isOk());
+    }
 }
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtilTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PatchDataTransactionUtilTest.java
new file mode 100644 (file)
index 0000000..283db9b
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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.restconf.restful.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.util.concurrent.Futures;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+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.PATCHStatusContext;
+import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusEntity;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.RestConnectorProvider;
+import org.opendaylight.restconf.common.references.SchemaContextRef;
+import org.opendaylight.restconf.handlers.TransactionChainHandler;
+import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+public class PatchDataTransactionUtilTest {
+
+    private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
+
+    @Mock
+    private DOMTransactionChain transactionChain;
+
+    @Mock
+    private DOMDataReadWriteTransaction rWTransaction;
+
+    private SchemaContextRef refSchemaCtx;
+    private YangInstanceIdentifier iIDCreateAndDelete;
+    private YangInstanceIdentifier iIDMerge;
+    private ContainerNode buildBaseContainerForTests;
+    private YangInstanceIdentifier targetNodeForCreateAndDelete;
+    private YangInstanceIdentifier targetNodeMerge;
+    private MapNode buildArtistList;
+
+    // Fields used when delete operation fails to reset transaction chain
+    private static Field handler;
+    private static Field broker;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+
+        PatchDataTransactionUtilTest.handler = RestConnectorProvider.class.getDeclaredField("transactionChainHandler");
+        PatchDataTransactionUtilTest.broker = RestConnectorProvider.class.getDeclaredField("dataBroker");
+
+        PatchDataTransactionUtilTest.handler.setAccessible(true);
+        PatchDataTransactionUtilTest.handler.set(RestConnectorProvider.class, mock(TransactionChainHandler.class));
+
+        PatchDataTransactionUtilTest.broker.setAccessible(true);
+        PatchDataTransactionUtilTest.broker.set(RestConnectorProvider.class, mock(DOMDataBroker.class));
+
+        refSchemaCtx = new SchemaContextRef(TestRestconfUtils.loadSchemaContext(PATH_FOR_NEW_SCHEMA_CONTEXT));
+        final QName baseQName = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
+        final QName containerPlayerQName = QName.create(baseQName, "player");
+        final QName leafGapQName = QName.create(baseQName, "gap");
+        final QName containerLibraryQName = QName.create(baseQName, "library");
+        final QName listArtistQName = QName.create(baseQName, "artist");
+        final QName leafNameQName = QName.create(baseQName, "name");
+        final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeWithKey =
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(listArtistQName, leafNameQName, "name of artist");
+
+        /** instance identifier for accessing leaf node "gap" */
+        iIDCreateAndDelete = YangInstanceIdentifier.builder()
+                .node(baseQName)
+                .node(containerPlayerQName)
+                .node(leafGapQName)
+                .build();
+
+        /** values that are used for creating leaf for testPatchDataCreateAndDelete test */
+        final LeafNode buildGapLeaf = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(leafGapQName))
+                .withValue(0.2)
+                .build();
+
+        final ContainerNode buildPlayerContainer = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(containerPlayerQName))
+                .withChild(buildGapLeaf)
+                .build();
+
+        buildBaseContainerForTests = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(baseQName))
+                .withChild(buildPlayerContainer)
+                .build();
+
+        targetNodeForCreateAndDelete = YangInstanceIdentifier.builder(iIDCreateAndDelete)
+                .node(containerPlayerQName)
+                .node(leafGapQName)
+                .build();
+
+        /** instance identifier for accessing leaf node "name" in list "artist" */
+        iIDMerge = YangInstanceIdentifier.builder()
+                .node(baseQName)
+                .node(containerLibraryQName)
+                .node(listArtistQName)
+                .nodeWithKey(listArtistQName, QName.create(listArtistQName, "name"), "name of artist")
+                .node(leafNameQName)
+                .build();
+
+        /** values that are used for creating leaf for testPatchDataReplaceMergeAndRemove test */
+        final LeafNode<Object> contentName = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "name")))
+                .withValue("name of artist")
+                .build();
+
+        final LeafNode<Object> contentDescription = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "description")))
+                .withValue("description of artist")
+                .build();
+
+        final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
+                .withNodeIdentifier(nodeWithKey)
+                .withChild(contentName)
+                .withChild(contentDescription)
+                .build();
+
+        buildArtistList = Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listArtistQName))
+                .withChild(mapEntryNode)
+                .build();
+
+        targetNodeMerge = YangInstanceIdentifier.builder()
+                .node(baseQName)
+                .node(containerLibraryQName)
+                .node(listArtistQName)
+                .nodeWithKey(listArtistQName, leafNameQName, "name of artist")
+                .build();
+
+        /** Mocks */
+        doReturn(rWTransaction).when(transactionChain).newReadWriteTransaction();
+        doReturn(Futures.immediateCheckedFuture(null)).when(rWTransaction).submit();
+    }
+
+    @Test
+    public void testPatchDataReplaceMergeAndRemove() {
+        doReturn(Futures.immediateCheckedFuture(false)).doReturn(Futures.immediateCheckedFuture(true))
+                .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeMerge);
+
+        final PATCHEntity entityReplace = new PATCHEntity("edit1", "REPLACE", targetNodeMerge, buildArtistList);
+        final PATCHEntity entityMerge = new PATCHEntity("edit2", "MERGE", targetNodeMerge, buildArtistList);
+        final PATCHEntity entityRemove = new PATCHEntity("edit3", "REMOVE", targetNodeMerge);
+        final List<PATCHEntity> entities = new ArrayList<>();
+
+        entities.add(entityReplace);
+        entities.add(entityMerge);
+        entities.add(entityRemove);
+
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+                new InstanceIdentifierContext<>(iIDMerge, null, null, refSchemaCtx.get());
+        final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchRMRm");
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+        final PATCHStatusContext patchStatusContext =
+                PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+        for (PATCHStatusEntity entity : patchStatusContext.getEditCollection()) {
+            assertTrue(entity.isOk());
+        }
+        assertTrue(patchStatusContext.isOk());
+    }
+
+    @Test
+    public void testPatchDataCreateAndDelete() throws Exception {
+        doReturn(Futures.immediateCheckedFuture(false)).doReturn(Futures.immediateCheckedFuture(true))
+                .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
+
+        final PATCHEntity entityCreate =
+                new PATCHEntity("edit1", "CREATE", targetNodeForCreateAndDelete, buildBaseContainerForTests);
+        final PATCHEntity entityDelete =
+                new PATCHEntity("edit2", "DELETE", targetNodeForCreateAndDelete);
+        final List<PATCHEntity> entities = new ArrayList<>();
+
+        entities.add(entityCreate);
+        entities.add(entityDelete);
+
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+                new InstanceIdentifierContext<>(iIDCreateAndDelete, null, null, refSchemaCtx.get());
+        final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchCD");
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+        final PATCHStatusContext patchStatusContext = PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+        for (PATCHStatusEntity entity : patchStatusContext.getEditCollection()) {
+            assertTrue(entity.isOk());
+        }
+        assertTrue(patchStatusContext.isOk());
+    }
+
+    @Test
+    public void deleteNonexistentDataTest() {
+        doReturn(Futures.immediateCheckedFuture(false))
+                .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
+
+        final PATCHEntity entityDelete =
+                new PATCHEntity("edit", "DELETE", targetNodeForCreateAndDelete);
+        final List<PATCHEntity> entities = new ArrayList<>();
+
+        entities.add(entityDelete);
+
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+                new InstanceIdentifierContext<>(iIDCreateAndDelete, null, null, refSchemaCtx.get());
+        final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchD");
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+        final PATCHStatusContext patchStatusContext = PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+        assertFalse(patchStatusContext.isOk());
+        assertEquals(RestconfError.ErrorType.PROTOCOL,
+                patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorType());
+        assertEquals(RestconfError.ErrorTag.DATA_MISSING,
+                patchStatusContext.getEditCollection().get(0).getEditErrors().get(0).getErrorTag());
+    }
+
+    @Test
+    public void testPatchMergePutContainer() throws Exception {
+        doReturn(Futures.immediateCheckedFuture(false)).doReturn(Futures.immediateCheckedFuture(true))
+                .when(rWTransaction).exists(LogicalDatastoreType.CONFIGURATION, targetNodeForCreateAndDelete);
+
+        final PATCHEntity entityMerge =
+                new PATCHEntity("edit1", "MERGE", targetNodeForCreateAndDelete, buildBaseContainerForTests);
+        final List<PATCHEntity> entities = new ArrayList<>();
+
+        entities.add(entityMerge);
+
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext =
+                new InstanceIdentifierContext<>(iIDCreateAndDelete, null, null, refSchemaCtx.get());
+        final PATCHContext patchContext = new PATCHContext(iidContext, entities, "patchM");
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(iidContext, null, transactionChain);
+        final PATCHStatusContext patchStatusContext = PatchDataTransactionUtil.patchData(patchContext, wrapper, refSchemaCtx);
+
+        for (PATCHStatusEntity entity : patchStatusContext.getEditCollection()) {
+            assertTrue(entity.isOk());
+        }
+        assertTrue(patchStatusContext.isOk());
+    }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/iid-value.yang b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/iid-value.yang
new file mode 100644 (file)
index 0000000..4e536df
--- /dev/null
@@ -0,0 +1,23 @@
+module iid-value-module {
+    namespace "iid:value:module";
+
+    prefix "iidvm";
+    revision 2016-09-12 {
+    }
+
+    container cont-iid {
+        list iid-list{
+            key "iid-leaf";
+            leaf iid-leaf{
+                type instance-identifier;
+            }
+        }
+
+        list values-iid{
+            key "value-iid";
+            leaf value-iid{
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlDataFindBarContainer.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlDataFindBarContainer.xml
new file mode 100644 (file)
index 0000000..6523345
--- /dev/null
@@ -0,0 +1,10 @@
+<!--
+  ~ 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
+  -->
+
+<foo-bar-container xmlns="bar:module">
+</foo-bar-container>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlDataFindFooContainer.xml b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/xml/xmlDataFindFooContainer.xml
new file mode 100644 (file)
index 0000000..93aeff8
--- /dev/null
@@ -0,0 +1,10 @@
+<!--
+  ~ 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
+  -->
+
+<foo-bar-container xmlns="foo:module">
+</foo-bar-container>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/bar-module.yang b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/bar-module.yang
new file mode 100644 (file)
index 0000000..90de085
--- /dev/null
@@ -0,0 +1,11 @@
+module bar-module {
+  namespace "bar:module";
+
+  prefix "bar-module";
+    revision 2016-09-29 {
+  }
+
+  /* This container has the same name as container in foo-module */
+  container foo-bar-container {
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/foo-module.yang b/restconf/sal-rest-connector/src/test/resources/instanceidentifier/yang/foo-module.yang
new file mode 100644 (file)
index 0000000..16b8e7f
--- /dev/null
@@ -0,0 +1,11 @@
+module foo-module {
+  namespace "foo:module";
+
+  prefix "foo-module";
+    revision 2016-09-29 {
+  }
+
+  /* This container has the same name as container in bar-module */
+  container foo-bar-container {
+  }
+}
\ No newline at end of file
index 6cafc73c7d16f21dd297b0b5cc4b267982d9735c..7f533877526aa89f5c54cf8097b56d9c7f3b07cb 100644 (file)
@@ -30,8 +30,8 @@
       <artifactId>sal-core-api</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.opendaylight.controller</groupId>
-      <artifactId>sal-remote</artifactId>
+      <groupId>org.opendaylight.netconf</groupId>
+      <artifactId>sal-rest-connector</artifactId>
     </dependency>
     <dependency>
       <groupId>org.opendaylight.yangtools</groupId>