Merge changes Ife737bb3,Ifa1e8774
authorTomas Cere <tcere@cisco.com>
Mon, 20 Jun 2016 09:00:18 +0000 (09:00 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 20 Jun 2016 09:00:18 +0000 (09:00 +0000)
* changes:
  Bug 5527: Unit testing - serializer
  Bug 5527: Unit testing - codec

28 files changed:
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/ClusteredNetconfTopology.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/ClusteredNetconfDevice.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/ClusteredNetconfDeviceCommunicator.java
netconf/netconf-topology/src/test/java/org/opendaylight/netconf/topology/pipeline/ClusteredNetconfDeviceCommunicatorTest.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java
netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang
netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java
netconf/tools/netconf-cli/src/main/java/org/opendaylight/netconf/cli/NetconfDeviceConnectionManager.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/client/stress/Parameters.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/client/stress/StressClientCallable.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/parser/ParserIdentifier.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/schema/context/RestconfSchemaUtilTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-empty-grouping-restconf.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-container-modules.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-grouping-restconf.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-module.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-stream.yang
restconf/sal-rest-connector/src/test/resources/parser-identifier/mount-point.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test-included.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/parser-identifier/test-module.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test-included.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test.yang [new file with mode: 0644]

index 477c3b661f36c544873cafb06c529ccf0cf12151..6d5cdf898b68539ecef02d8ed7fc64e214651a2d 100644 (file)
@@ -88,6 +88,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology, Bindin
     protected static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 60000L;
     protected static final int DEFAULT_KEEPALIVE_DELAY = 0;
     protected static final boolean DEFAULT_RECONNECT_ON_CHANGED_SCHEMA = false;
+    protected static final int DEFAULT_CONCURRENT_RPC_LIMIT = 0;
     private static final int DEFAULT_MAX_CONNECTION_ATTEMPTS = 0;
     private static final int DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS = 2000;
     private static final long DEFAULT_CONNECTION_TIMEOUT_MILLIS = 20000L;
@@ -325,13 +326,18 @@ public abstract class AbstractNetconfTopology implements NetconfTopology, Bindin
         }
 
         final Optional<NetconfSessionPreferences> userCapabilities = getUserCapabilities(node);
+        final int rpcMessageLimit =
+                node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
+
+        if (rpcMessageLimit < 1) {
+            LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
+        }
 
         return new NetconfConnectorDTO(
                 userCapabilities.isPresent() ?
                         new NetconfDeviceCommunicator(
-                                remoteDeviceId, device, new UserPreferences(userCapabilities.get(), node.getYangModuleCapabilities().isOverride())):
-                        new NetconfDeviceCommunicator(remoteDeviceId, device)
-                , salFacade);
+                                remoteDeviceId, device, new UserPreferences(userCapabilities.get(), node.getYangModuleCapabilities().isOverride()), rpcMessageLimit):
+                        new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade);
     }
 
     protected NetconfDevice.SchemaResourcesDTO setupSchemaCacheDTO(final NodeId nodeId, final NetconfNode node) {
index 1612c7d9fccb23af2e7238145a91d232bc722772..ec6e4fec333b8921c00d4d6e57d12de75674dc51 100644 (file)
@@ -161,7 +161,14 @@ public class ClusteredNetconfTopology extends AbstractNetconfTopology implements
         final NetconfDevice device = new ClusteredNetconfDevice(schemaResourcesDTO, remoteDeviceId, salFacade,
                 processingExecutor.getExecutor(), actorSystem, topologyId, nodeId.getValue(), TypedActor.context());
 
-        return new NetconfConnectorDTO(new ClusteredNetconfDeviceCommunicator(remoteDeviceId, device, entityOwnershipService), salFacade);
+        final int rpcMessageLimit =
+                node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
+
+        if (rpcMessageLimit < 1) {
+            LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
+        }
+
+        return new NetconfConnectorDTO(new ClusteredNetconfDeviceCommunicator(remoteDeviceId, device, entityOwnershipService, rpcMessageLimit), salFacade);
     }
 
     @Override
index a94bb90cd7927f5eed670a953b0cd59a427297a5..0710769c8c9ead597add94654853b772e1ab78cc 100644 (file)
@@ -48,7 +48,6 @@ public class ClusteredNetconfDevice extends NetconfDevice implements EntityOwner
 
     private static final Logger LOG = LoggerFactory.getLogger(ClusteredNetconfDevice.class);
 
-    private boolean isMaster = false;
     private NetconfDeviceCommunicator listener;
     private NetconfSessionPreferences sessionPreferences;
     private SchemaRepository schemaRepo;
index 0b191974d761e352de8b35e20a01c5e2251df3de..061024c1caa9478fc2fc7c50303e3409ab97a86b 100644 (file)
@@ -26,8 +26,8 @@ public class ClusteredNetconfDeviceCommunicator extends NetconfDeviceCommunicato
     private final ArrayList<NetconfClientSessionListener> netconfClientSessionListeners = new ArrayList<>();
     private EntityOwnershipListenerRegistration ownershipListenerRegistration = null;
 
-    public ClusteredNetconfDeviceCommunicator(RemoteDeviceId id, NetconfDevice remoteDevice, EntityOwnershipService ownershipService) {
-        super(id, remoteDevice);
+    public ClusteredNetconfDeviceCommunicator(RemoteDeviceId id, NetconfDevice remoteDevice, EntityOwnershipService ownershipService, final int rpcMessageLimit) {
+        super(id, remoteDevice, rpcMessageLimit);
         this.ownershipService = ownershipService;
     }
 
index 64fe5fad502207fce11d1ce62b81216a7d0bc703..aa6b69da51e5ccc3727e01f0c629aaa4e8c8eb65 100644 (file)
@@ -57,7 +57,7 @@ public class ClusteredNetconfDeviceCommunicatorTest {
         doReturn(ownershipListenerRegistration).when(ownershipService).registerListener(
                 "netconf-node/" + REMOTE_DEVICE_ID.getName(), remoteDevice);
 
-        communicator = new ClusteredNetconfDeviceCommunicator(REMOTE_DEVICE_ID, remoteDevice, ownershipService);
+        communicator = new ClusteredNetconfDeviceCommunicator(REMOTE_DEVICE_ID, remoteDevice, ownershipService, 10);
         communicator.registerNetconfClientSessionListener(listener1);
         communicator.registerNetconfClientSessionListener(listener2);
     }
index 47da05f36be0ce3c1af0858b776f7a61f6d3045a..79a439c0ebc5984f00c0a70616fcbd7cc9920833 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.controller.config.yang.md.sal.connector.netconf;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkCondition;
 import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkNotNull;
 
@@ -294,10 +295,14 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
                 .setSalFacade(salFacade)
                 .build();
 
+        if (getConcurrentRpcLimit() < 1) {
+            LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", id);
+        }
+
         final NetconfDeviceCommunicator listener = userCapabilities.isPresent() ?
                 new NetconfDeviceCommunicator(id, device,
-                        new UserPreferences(userCapabilities.get(), getYangModuleCapabilities().getOverride())):
-                new NetconfDeviceCommunicator(id, device);
+                        new UserPreferences(userCapabilities.get(), getYangModuleCapabilities().getOverride()), getConcurrentRpcLimit()):
+                new NetconfDeviceCommunicator(id, device, getConcurrentRpcLimit());
 
         if (shouldSendKeepalive()) {
             ((KeepaliveSalFacade) salFacade).setListener(listener);
index e979071d873f3c7c2770c545cfa9cbfc65480cc9..4fbd6f624145eac163c8bdb2cc61b3384782907d 100644 (file)
@@ -20,6 +20,7 @@ import java.util.ArrayDeque;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Queue;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import org.opendaylight.controller.config.util.xml.XmlElement;
@@ -53,7 +54,9 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
     protected final RemoteDeviceId id;
     private final Lock sessionLock = new ReentrantLock();
 
-    // TODO implement concurrent message limit
+    private final Semaphore semaphore;
+    private final int concurentRpcMsgs;
+
     private final Queue<Request> requests = new ArrayDeque<>();
     private NetconfClientSession session;
 
@@ -61,21 +64,24 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
     private SettableFuture<NetconfDeviceCapabilities> firstConnectionFuture;
 
     public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> remoteDevice,
-            final UserPreferences NetconfSessionPreferences) {
-        this(id, remoteDevice, Optional.of(NetconfSessionPreferences));
+            final UserPreferences NetconfSessionPreferences, final int rpcMessageLimit) {
+        this(id, remoteDevice, Optional.of(NetconfSessionPreferences), rpcMessageLimit);
     }
 
     public NetconfDeviceCommunicator(final RemoteDeviceId id,
-                                     final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> remoteDevice) {
-        this(id, remoteDevice, Optional.<UserPreferences>absent());
+                                     final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> remoteDevice,
+                                     final int rpcMessageLimit) {
+        this(id, remoteDevice, Optional.<UserPreferences>absent(), rpcMessageLimit);
     }
 
     private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> remoteDevice,
-            final Optional<UserPreferences> overrideNetconfCapabilities) {
+                                      final Optional<UserPreferences> overrideNetconfCapabilities, final int rpcMessageLimit) {
+        this.concurentRpcMsgs = rpcMessageLimit;
         this.id = id;
         this.remoteDevice = remoteDevice;
         this.overrideNetconfCapabilities = overrideNetconfCapabilities;
         this.firstConnectionFuture = SettableFuture.create();
+        this.semaphore = rpcMessageLimit > 0 ? new Semaphore(rpcMessageLimit) : null;
     }
 
     @Override
@@ -245,6 +251,11 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
             request = requests.peek();
             if (request != null && request.future.isUncancellable()) {
                 requests.poll();
+                // we have just removed one request from the queue
+                // we can also release one permit
+                if(semaphore != null) {
+                    semaphore.release();
+                }
             } else {
                 request = null;
                 LOG.warn("{}: Ignoring unsolicited message {}", id,
@@ -302,8 +313,17 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
     @Override
     public ListenableFuture<RpcResult<NetconfMessage>> sendRequest(final NetconfMessage message, final QName rpc) {
         sessionLock.lock();
+
+        if (semaphore != null && !semaphore.tryAcquire()) {
+            LOG.warn("Limit of concurrent rpc messages was reached (limit :" +
+                    concurentRpcMsgs + "). Rpc reply message is needed. Discarding request of Netconf device with id" + id.getName());
+            sessionLock.unlock();
+            return Futures.immediateFailedFuture(new NetconfDocumentedException("Limit of rpc messages was reached (Limit :" +
+                    concurentRpcMsgs + ") waiting for emptying the queue of Netconf device with id" + id.getName()));
+        }
+
         try {
-            return sendRequestWithLock( message, rpc );
+            return sendRequestWithLock(message, rpc);
         } finally {
             sessionLock.unlock();
         }
index 2c327d495e7781e7dfa91df84b48d0297be2fd36..94ce4e792102e92686fbeddd66c67d1236c52e0d 100644 (file)
@@ -118,6 +118,14 @@ module netconf-node-topology {
             description "Netconf connector sends keepalive RPCs while the session is idle, this delay specifies the delay between keepalive RPC in seconds
                          If a value <1 is provided, no keepalives will be sent";
         }
+
+        leaf concurrent-rpc-limit {
+            config true;
+            type uint16;
+            default 0;
+            description "Limit of concurrent messages that can be send before reply messages are received.
+                         If value <1 is provided, no limit will be enforced";
+        }
     }
 
     grouping netconf-node-connection-status {
index a7bb0827148efc97b99e6b96469c2086c382baa7..846c8fe65a9a10e96d134dc198b3a166da29f9f5 100644 (file)
@@ -63,6 +63,13 @@ module odl-sal-netconf-connector-cfg {
                 type string;
             }
 
+            leaf concurrent-rpc-limit {
+                type uint16;
+                default 0;
+                description "Limit of concurrent messages that can be send before reply messages are received.
+                             If value less than 1 is provided, no limit will be enforced";
+            }
+
             leaf schema-cache-directory {
                 type string;
                 default "schema";
index 6f69c84e32530a64ef8e0f180229c151a6cfd14f..7938f429a65c3b16bf0c6d85329777b04f0661e5 100644 (file)
@@ -11,6 +11,7 @@ package org.opendaylight.netconf.sal.connect.netconf.listener;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.same;
@@ -37,6 +38,7 @@ import io.netty.util.concurrent.GenericFutureListener;
 import io.netty.util.concurrent.GlobalEventExecutor;
 import java.io.ByteArrayInputStream;
 import java.net.InetSocketAddress;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.UUID;
@@ -84,7 +86,7 @@ public class NetconfDeviceCommunicatorTest {
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks( this );
 
-        communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice);
+        communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice, 10);
     }
 
     void setupSession() {
@@ -95,11 +97,11 @@ public class NetconfDeviceCommunicatorTest {
     }
 
     private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
-        return sendRequest( UUID.randomUUID().toString() );
+        return sendRequest( UUID.randomUUID().toString(), true );
     }
 
     @SuppressWarnings("unchecked")
-    private ListenableFuture<RpcResult<NetconfMessage>> sendRequest( final String messageID ) throws Exception {
+    private ListenableFuture<RpcResult<NetconfMessage>> sendRequest( final String messageID, final boolean doLastTest ) throws Exception {
         Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
         Element element = doc.createElement( "request" );
         element.setAttribute( "message-id", messageID );
@@ -113,8 +115,9 @@ public class NetconfDeviceCommunicatorTest {
 
         ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
                                       communicator.sendRequest( message, QName.create( "mock rpc" ) );
-
-        assertNotNull( "ListenableFuture is null", resultFuture );
+        if(doLastTest) {
+            assertNotNull("ListenableFuture is null", resultFuture);
+        }
         return resultFuture;
     }
 
@@ -301,13 +304,13 @@ public class NetconfDeviceCommunicatorTest {
         setupSession();
 
         String messageID1 = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1 );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1, true );
 
         String messageID2 = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2 );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2, true );
 
         String messageID3 = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest( messageID3 );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest( messageID3, true );
 
         //response messages 1,2 are omitted
         communicator.onMessage( mockSession, createSuccessResponseMessage( messageID3 ) );
@@ -320,10 +323,10 @@ public class NetconfDeviceCommunicatorTest {
         setupSession();
 
         String messageID1 = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1 );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture1 = sendRequest( messageID1, true );
 
         String messageID2 = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2 );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest( messageID2, true );
 
         communicator.onMessage( mockSession, createSuccessResponseMessage( messageID1 ) );
         communicator.onMessage( mockSession, createSuccessResponseMessage( messageID2 ) );
@@ -337,7 +340,7 @@ public class NetconfDeviceCommunicatorTest {
         setupSession();
 
         String messageID = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID, true );
 
         communicator.onMessage( mockSession, createErrorResponseMessage( messageID ) );
 
@@ -381,7 +384,7 @@ public class NetconfDeviceCommunicatorTest {
         final EventLoopGroup group = new NioEventLoopGroup();
         final Timer time = new HashedWheelTimer();
         try {
-            final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), device);
+            final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), device, 10);
             final NetconfReconnectingClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create()
                     .withAddress(new InetSocketAddress("localhost", 65000))
                     .withReconnectStrategy(reconnectStrategy)
@@ -411,7 +414,7 @@ public class NetconfDeviceCommunicatorTest {
         setupSession();
 
         String messageID = UUID.randomUUID().toString();
-        ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID );
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest( messageID, true );
 
         communicator.onMessage( mockSession, createSuccessResponseMessage( UUID.randomUUID().toString() ) );
 
@@ -428,6 +431,27 @@ public class NetconfDeviceCommunicatorTest {
                       errorInfo.contains( "expected-message-id" ) );
     }
 
+    @Test
+    public void testConcurrentMessageLimit() throws Exception {
+        setupSession();
+        ArrayList<String> messageID = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            messageID.add(UUID.randomUUID().toString());
+            ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID.get(i), false);
+            assertEquals("ListenableFuture is null", true, resultFuture instanceof UncancellableFuture);
+        }
+
+        final String notWorkingMessageID = UUID.randomUUID().toString();
+        ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(notWorkingMessageID, false);
+        assertEquals("ListenableFuture is null", false, resultFuture instanceof UncancellableFuture);
+
+        communicator.onMessage(mockSession, createSuccessResponseMessage(messageID.get(0)));
+
+        resultFuture = sendRequest(messageID.get(0), false);
+        assertNotNull("ListenableFuture is null", resultFuture);
+    }
+
     private static NetconfMessage createErrorResponseMessage( final String messageID ) throws Exception {
         String xmlStr =
             "<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"" +
index 2a73412d2c20166e51166f1cbb2701761aeeaef0..4bbbc11dfcdf62b2fcbc69e40718e9583c41565a 100644 (file)
@@ -48,6 +48,7 @@ public class NetconfDeviceConnectionManager implements Closeable {
     private final NetconfClientDispatcherImpl netconfClientDispatcher;
 
     private static final String CACHE = "cache/schema";
+    private static final int RPC_LIMIT = 0;
 
     // Connection
     private NetconfDeviceConnectionHandler handler;
@@ -92,7 +93,8 @@ public class NetconfDeviceConnectionManager implements Closeable {
                 .setId(deviceId)
                 .setSalFacade(handler)
                 .build();
-        listener = new NetconfDeviceCommunicator(deviceId, device);
+        listener = new NetconfDeviceCommunicator(deviceId, device, RPC_LIMIT);
+
         configBuilder.withSessionListener(listener);
         listener.initializeRemoteConnection(netconfClientDispatcher, configBuilder.build());
     }
index 77a0a60a301fd66128cf9a31dcf0b6cf4e0f4a68..fdd2202678dac0c0e6d2122e00f5a2fad46ea2c4 100644 (file)
@@ -67,6 +67,9 @@ public class Parameters {
     @Arg(dest = "thread-amount")
     public int threadAmount;
 
+    @Arg(dest = "concurrent-message-limit")
+    public int concurrentMessageLimit;
+
     static ArgumentParser getParser() {
         final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf stress client");
 
@@ -163,6 +166,12 @@ public class Parameters {
                 .setDefault(1)
                 .dest("thread-amount");
 
+        parser.addArgument("--concurrent-message-limit")
+                .type(Integer.class)
+                .setDefault(0)
+                .help("Number of rpc messages that can be sent before receiving reply to them.")
+                .dest("concurrent-message-limit");
+
         return parser;
     }
 
index 501ef8c71e70f245f11958188e1c394878cf1c65..39161c3769737a191ae9b269e049f98bbdb749c0 100644 (file)
@@ -43,7 +43,7 @@ public class StressClientCallable implements Callable<Boolean>{
                                 final NetconfClientDispatcherImpl netconfClientDispatcher,
                                 final List<NetconfMessage> preparedMessages) {
         this.params = params;
-        this.sessionListener = getSessionListener(params.getInetAddress());
+        this.sessionListener = getSessionListener(params.getInetAddress(), params.concurrentMessageLimit);
         this.netconfClientDispatcher = netconfClientDispatcher;
         cfg = getNetconfClientConfiguration(this.params, this.sessionListener);
 
@@ -73,9 +73,9 @@ public class StressClientCallable implements Callable<Boolean>{
         }
     }
 
-    private static NetconfDeviceCommunicator getSessionListener(final InetSocketAddress inetAddress) {
+    private static NetconfDeviceCommunicator getSessionListener(final InetSocketAddress inetAddress, final int messageLimit) {
         final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> loggingRemoteDevice = new StressClient.LoggingRemoteDevice();
-        return new NetconfDeviceCommunicator(new RemoteDeviceId("secure-test", inetAddress), loggingRemoteDevice);
+        return new NetconfDeviceCommunicator(new RemoteDeviceId("secure-test", inetAddress), loggingRemoteDevice, messageLimit);
     }
 
     private static NetconfClientConfiguration getNetconfClientConfiguration(final Parameters params, final NetconfDeviceCommunicator sessionListener) {
index 9f1dcb4e066c0e370086af918b944db2f181a800..891784a29903aa7e3f27fc64548c9ae94f0a6f84 100644 (file)
@@ -72,18 +72,28 @@ public final class ParserIdentifier {
      * @return {@link QName}
      */
     public static QName makeQNameFromIdentifier(final String identifier) {
+        // check if more than one slash is not used as path separator
+        if (identifier.contains(
+                String.valueOf(RestconfConstants.SLASH).concat(String.valueOf(RestconfConstants.SLASH)))) {
+            LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' " + identifier);
+            throw new RestconfDocumentedException(
+                    "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
+                    ErrorTag.INVALID_VALUE);
+        }
+
         final int mountIndex = identifier.indexOf(RestconfConstants.MOUNT);
         String moduleNameAndRevision = "";
         if (mountIndex >= 0) {
-            moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length());
+            moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length())
+                    .replaceFirst(String.valueOf(RestconfConstants.SLASH), "");
         } else {
             moduleNameAndRevision = identifier;
         }
 
-        final Splitter splitter = Splitter.on("/").omitEmptyStrings();
+        final Splitter splitter = Splitter.on(RestconfConstants.SLASH);
         final Iterable<String> split = splitter.split(moduleNameAndRevision);
-        final List<String> pathArgs = Lists.<String> newArrayList(split);
-        if (pathArgs.size() < 2) {
+        final List<String> pathArgs = Lists.newArrayList(split);
+        if (pathArgs.size() != 2) {
             LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' " + identifier);
             throw new RestconfDocumentedException(
                     "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java
new file mode 100644 (file)
index 0000000..7ba1bac
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * 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.parser.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link YangInstanceIdentifierDeserializer}
+ */
+public class YangInstanceIdentifierDeserializerTest {
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    // schema context
+    private SchemaContext schemaContext;
+
+    @Before
+    public void init() throws Exception {
+        schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser/deserializer");
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI with container to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeContainerTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:contA");
+
+        assertEquals("Result does not contains expected number of path arguments", 1, Iterables.size(result));
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+                result.iterator().next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI with container containing leaf to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeContainerWithLeafTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:contA/leaf-A");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI with container containing list with leaf list to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeContainerWithListWithLeafListTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:contA/list-A=100/leaf-list-AA=instance");
+
+        assertEquals("Result does not contains expected number of path arguments", 5, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        // container
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+                iterator.next());
+
+        // list
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-A");
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                        list, QName.create(list, "list-key"), 100).toString(),
+                iterator.next().toString());
+
+        // leaf list
+        final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-AA");
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(leafList),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeWithValue(leafList, "instance"),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing list with no keys to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeListWithNoKeysTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-no-key");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing list with one key to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeListWithOneKeyTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-one-key=value");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, QName.create(list, "name"), "value"),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing list with multiple keys to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeListWithMultipleKeysTest() {
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), "value");
+        values.put(QName.create(list, "number"), 100);
+        values.put(QName.create(list, "enabled"), false);
+
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-multiple-keys=value,100,false");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+                iterator.next().toString());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing leaf list to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeLeafListTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:leaf-list-0=true");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
+
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifier(leafList),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeWithValue(leafList, true).toString(),
+                iterator.next().toString());
+    }
+
+    /**
+     * Negative test when supplied <code>SchemaContext</code> is null. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void deserializeNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        YangInstanceIdentifierDeserializer.create(null, "/deserializer-test:contA");
+    }
+
+    /**
+     * Negative test when supplied <code>String</code> data to deserialize is null. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void nullDataNegativeNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, null);
+    }
+
+    /**
+     * Negative test when empty <code>String</code> is supplied as an input. Test is expected to fail with
+     * <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void deserializeEmptyDataNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "");
+    }
+
+    /**
+     * Negative test when identifier is not followed by slash or equals. Test is expected to fail with
+     * <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void deserializeBadCharMissingSlashOrEqualNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:cont*leaf-A");
+    }
+
+    /**
+     * Negative test of validating identifier when identifier does not start with slash.
+     * <code>IllegalArgumentException</code> is expected.
+     */
+    @Test
+    public void deserializeNoBeginningSlashNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA");
+    }
+
+    /**
+     * Positive test of validating identifier when identifier contains slash only. Deserialization should return
+     * empty result.
+     */
+    @Test
+    public void validArgOnlySlashTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/");
+        assertTrue("Result does not contains expected number of path arguments", Iterables.isEmpty(result));
+    }
+
+    /**
+     * Negative test of validating identifier when there is a slash after container without next identifier. Test
+     * is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/");
+    }
+
+    /**
+     * Negative test of validating identifier when there is a slash after list key values without next identifier. Test
+     * is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void validArgIdentifierListEndsWithSlashLNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key=value/");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when identifier is empty (example: '//'). Test is expected to fail
+     * with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameEmptyIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "//");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when two identifiers are separated by two slashes. Test is
+     * expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameTwoSlashesNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA//leaf-A");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when in identifier there is another sign than colon or equals.
+     * Test is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameBuildPathNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test*contA");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when it is not possible to find module for specified prefix. Test is
+     * expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameNotExistingPrefixNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/not-existing:contA");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when after prefix and colon there is not parsable identifier as
+     * local name. Test is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:*not-parsable-identifier");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when data ends after prefix and colon. Test is expected to fail
+     * with <code>StringIndexOutOfBoundsException</code>.
+     */
+    @Test
+    public void prepareQnameErrorParsingNegativeTest() {
+        thrown.expect(StringIndexOutOfBoundsException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when after identifier and colon there is node name of unknown
+     * node in current container. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+     * type, error tag and error status code are compared to expected values.
+     */
+    @Test
+    public void prepareQnameNotValidContainerNameNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/leafB");
+            fail("Test should fail due to unknown child node in container");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when after identifier and equals there is node name of unknown
+     * node in current list. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+     * type, error tag and error status code are compared to expected values.
+     */
+    @Test
+    public void prepareQnameNotValidListNameNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-no-key/disabled=false");
+            fail("Test should fail due to unknown child node in list");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of getting next identifier when current node is keyed entry. Test is expected to
+     * fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareIdentifierNotKeyedEntryNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key");
+    }
+
+    /**
+     * Negative test when there is a comma also after the last key. Test is expected to fail with
+     * <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void deserializeKeysEndsWithComaNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create( schemaContext,
+                "/deserializer-test:list-multiple-keys=value,100,false,");
+    }
+
+    /**
+     * Positive when not all keys of list are encoded. The missing keys should be considered to has empty
+     * <code>String</code> values. Also value of next leaf must not be considered to be missing key value.
+     */
+    @Test
+    public void notAllListKeysEncodedPositiveTest() {
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), ":foo");
+        values.put(QName.create(list, "number"), "");
+        values.put(QName.create(list, "enabled"), "");
+
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
+                schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo,,/string-value");
+
+        assertEquals("Result does not contains expected number of path arguments", 3, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        // list
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+                iterator.next().toString());
+
+        // leaf
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifier(
+                        QName.create("deserializer:test", "2016-06-06", "string-value")),
+                iterator.next());
+    }
+
+    /**
+     * Negative test when not all keys of list are encoded and it is not possible to consider missing keys to be empty.
+     * Test is expected to fail with <code>RestconfDocumentedException</code> and error type, error tag and error
+     * status code are compared to expected values.
+     */
+    @Test
+    public void notAllListKeysEncodedNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(
+                    schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo/string-value");
+            fail("Test should fail due to missing list key values");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.MISSING_ATTRIBUTE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of preparing node with predicates when it is not possible to get <code>DataSchemaNode</code>.
+     * Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Ignore
+    @Test
+    public void prepareNodeWithPredicatesNegativeTest() {}
+
+    /**
+     * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
+     * value should be complete also with not percent-encoded parts.
+     */
+    @Test
+    public void percentEncodedKeyEndsWithNoPercentEncodedChars() {
+        final String URI = "/deserializer-test:list-multiple-keys=%3Afoo,bar%3A,foo%3Abar";
+        final YangInstanceIdentifier result = YangInstanceIdentifier.create(
+                YangInstanceIdentifierDeserializer.create(schemaContext, URI));
+
+        final Iterator<Map.Entry<QName, Object>> resultListKeys = ((YangInstanceIdentifier.NodeIdentifierWithPredicates)
+                result.getLastPathArgument()).getKeyValues().entrySet().iterator();
+
+        assertEquals(":foo", resultListKeys.next().getValue());
+        assertEquals("bar:", resultListKeys.next().getValue());
+        assertEquals("foo:bar", resultListKeys.next().getValue());
+    }
+
+    /**
+     * Positive test when all keys of list can be considered to be empty <code>String</code>.
+     */
+    @Test
+    public void deserializeAllKeysEmptyTest() {
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), "");
+        values.put(QName.create(list, "number"), "");
+        values.put(QName.create(list, "enabled"), "");
+
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-multiple-keys=,,");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+                iterator.next().toString());
+    }
+
+    /**
+     * Negative test of deserialization when for leaf list there is no specified instance value.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void leafListMissingKeyNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:leaf-list-0=");
+            fail("Test should fail due to missing instance value");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.MISSING_ATTRIBUTE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Positive test of deserialization when parts of input URI <code>String</code> are defined in another module.
+     */
+    @Test
+    public void deserializePartInOtherModuleTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
+                schemaContext, "/deserializer-test-included:augmented-list=100/augmented-leaf");
+
+        assertEquals("Result does not contains expected number of path arguments", 4, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
+        final QName child = QName.create("deserializer:test", "2016-06-06", "augmented-leaf");
+
+        // list
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, QName.create(list, "list-key"), 100)
+                        .toString(),
+                iterator.next()
+                        .toString());
+
+        // augmented leaf
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)),
+                iterator.next());
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(child),
+                iterator.next());
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java
new file mode 100644 (file)
index 0000000..7793075
--- /dev/null
@@ -0,0 +1,635 @@
+/*
+ * 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.utils.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.broker.impl.mount.DOMMountPointServiceImpl;
+import org.opendaylight.controller.md.sal.dom.broker.spi.mount.SimpleDOMMountPoint;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link ParserIdentifier}
+ */
+public class ParserIdentifierTest {
+    // mount point identifier + expected result
+    private static final String MOUNT_POINT_IDENT =
+            "/mount-point:mount-container/point-number" + "/" + RestconfConstants.MOUNT;
+
+    private static final String MOUNT_POINT_IDENT_RESULT =
+            "/(mount:point?revision=2016-06-02)mount-container/point-number";
+
+    // invalid mount point identifier
+    private static final String INVALID_MOUNT_POINT_IDENT =
+            "/mount-point:point-number" + "/" + RestconfConstants.MOUNT;
+
+    // test identifier + expected result
+    private static final String TEST_IDENT =
+            "/parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
+
+    private static final String TEST_IDENT_RESULT =
+            "/(parser:identifier?revision=2016-06-02)cont1/cont2/listTest/listTest/list-in-grouping/"
+            + "list-in-grouping[{(parser:identifier?revision=2016-06-02)name=name}]/leaf-A.B";
+
+    // test identifier with nodes defined in other modules using augmentation + expected result
+    private static final String TEST_IDENT_OTHERS =
+            "/parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
+
+    private static final String TEST_IDENT_OTHERS_RESULT =
+            "/(parser:identifier:included?revision=2016-06-02)list-1/list-1"
+            + "[{(parser:identifier:included?revision=2016-06-02)name=name, "
+            + "(parser:identifier:included?revision=2016-06-02)revision=2016-06-02}]"
+            + "/AugmentationIdentifier{childNames=[(parser:identifier?revision=2016-06-02)augment-leaf]}/"
+            + "(parser:identifier?revision=2016-06-02)augment-leaf";
+
+    // invalid test identifier
+    private static final String INVALID_TEST_IDENT =
+            "/parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
+
+    // schema context with test modules
+    private SchemaContext schemaContext;
+
+    private static final String TEST_MODULE_NAME = "test-module";
+    private static final String TEST_MODULE_REVISION = "2016-06-02";
+    private static final String TEST_MODULE_NAMESPACE = "test:module";
+    private static final String MOUNT_POINT_IDENT_WITHOUT_SLASH = MOUNT_POINT_IDENT.replaceFirst("/", "");
+
+    // mount point and mount point service
+    private DOMMountPoint mountPoint;
+    private DOMMountPointService mountPointService;
+
+    // mock mount point and mount point service
+    @Mock DOMMountPoint mockMountPoint;
+    @Mock DOMMountPointService mockMountPointService;
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        schemaContext = TestRestconfUtils.loadSchemaContext("/parser-identifier");
+
+        // create and register mount point
+        mountPoint = SimpleDOMMountPoint.create(
+                YangInstanceIdentifier.builder()
+                        .node(QName.create("mount:point", "2016-06-02", "mount-container"))
+                        .node(QName.create("mount:point", "2016-06-02", "point-number"))
+                        .build(),
+                ImmutableClassToInstanceMap.copyOf(Maps.newHashMap()),
+                schemaContext
+        );
+
+        mountPointService = new DOMMountPointServiceImpl();
+        ((DOMMountPointServiceImpl) mountPointService).registerMountPoint(mountPoint);
+
+        // register mount point with null schema context
+        when(mockMountPoint.getSchemaContext()).thenReturn(null);
+        when(mockMountPointService.getMountPoint(YangInstanceIdentifier.EMPTY)).thenReturn(Optional.of(mockMountPoint));
+    }
+
+    /**
+     * {@link ParserIdentifier#toInstanceIdentifier(String, SchemaContext)} tests
+     */
+
+    /**
+     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when all nodes are defined
+     * in one module.
+     */
+    @Test
+    public void toInstanceIdentifierTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                TEST_IDENT, schemaContext);
+
+        assertEquals("Returned not expected identifier",
+                TEST_IDENT_RESULT, context .getInstanceIdentifier().toString());
+    }
+
+    /**
+     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when nodes are defined in
+     * multiple modules.
+     */
+    @Test
+    public void toInstanceIdentifierOtherModulesTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                TEST_IDENT_OTHERS, schemaContext);
+
+        assertEquals("Returned not expected identifier",
+                TEST_IDENT_OTHERS_RESULT, context.getInstanceIdentifier().toString());
+    }
+
+    /**
+     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier containing
+     * {@link RestconfConstants#MOUNT}.
+     */
+    @Test
+    public void toInstanceIdentifierMountPointTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                MOUNT_POINT_IDENT, schemaContext);
+
+        assertEquals("Returned not expected identifier",
+                MOUNT_POINT_IDENT_RESULT, context.getInstanceIdentifier().toString());
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> when identifier is <code>null</code>. Test
+     * fails expecting <code>NullPointerException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierNullIdentifierNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toInstanceIdentifier(null, schemaContext);
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> when <code>SchemaContext</code> is
+     * <code>null</code>. Test fails expecting <code>NullPointerException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toInstanceIdentifier(TEST_IDENT, null);
+    }
+
+    /**
+     * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+     */
+    @Test
+    public void toInstanceIdentifierSlashIdentifierTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier("/", schemaContext);
+        assertEquals("Returned not expected identifier",
+                YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
+    }
+
+    /**
+     * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+     * Test when identifier contains {@link RestconfConstants#MOUNT}.
+     */
+    @Test
+    public void toInstanceIdentifierSlashIdentifierMountPointTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                "/" + "/" + RestconfConstants.MOUNT, schemaContext);
+        assertEquals("Returned not expected identifier",
+                YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> with empty identifier.
+     * <code>IllegalArgumentException</code> is expected.
+     */
+    @Test
+    public void toInstanceIdentifierEmptyIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier("", schemaContext);
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> from identifier containing
+     * {@link RestconfConstants#MOUNT} when identifier part is empty. <code>IllegalArgumentException</code> is expected.
+     */
+    @Test
+    public void toInstanceIdentifierMountPointEmptyIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier("/" + RestconfConstants.MOUNT, schemaContext);
+    }
+
+    /**
+     * Negative test with invalid test identifier. Test should fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierInvalidIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier(INVALID_TEST_IDENT, schemaContext);
+    }
+
+    /**
+     * Negative test when identifier contains {@link RestconfConstants#MOUNT} but identifier part is not valid. Test
+     * should fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierMountPointInvalidIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier(INVALID_MOUNT_POINT_IDENT, schemaContext);
+    }
+
+    /**
+     * {@link ParserIdentifier#makeQNameFromIdentifier(String)} tests
+     */
+
+    /**
+     * Positive test of making <code>QName</code> from identifier and compare values from returned <code>QName</code>
+     * to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierTest() {
+        final QName qName = ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION);
+
+        assertNotNull("QName should be created", qName);
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, qName.getLocalName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, qName.getFormattedRevision());
+    }
+
+    /**
+     * Negative test when supplied identifier is in invalid format and then revision is not parsable.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME);
+            fail("Test should fail due to invalid identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test when supplied identifier is too short (contains only module name).
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierTooShortIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME);
+            fail("Test should fail due to too short identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Positive test of making <code>QName</code> from identifier for module behind mount point. Value from returned
+     * <code>QName</code> are compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierMountTest() {
+        final QName qName = ParserIdentifier.makeQNameFromIdentifier(
+                MOUNT_POINT_IDENT
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION);
+
+        assertNotNull("QName should be created", qName);
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, qName.getLocalName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, qName.getFormattedRevision());
+    }
+
+    /**
+     * Negative test when supplied identifier for module behind mount point is in invalid format and then revision is
+     * not parsable. <code>RestconfDocumentedException</code> is expected and error type, error tag and error status
+     * code are compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierMountPointInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(
+                    MOUNT_POINT_IDENT
+                    + "/"
+                    + TEST_MODULE_REVISION
+                    + "/"
+                    + TEST_MODULE_NAME);
+
+            fail("Test should fail due to invalid identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test when supplied identifier for module behind mount point is too short (contains only module name).
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierMountPointTooShortIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(
+                    MOUNT_POINT_IDENT
+                    + "/"
+                    + TEST_MODULE_NAME);
+
+            fail("Test should fail due to too short identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test trying to make <code>QName</code> from <code>null</code> identifier. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void makeQNameFromIdentifierNullIdentifierNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.makeQNameFromIdentifier(null);
+    }
+
+    /**
+     * Negative test trying to make <code>QName</code> from empty identifier. Test is expected to fail with
+     * <code>RestconfDocumentedException</code>. Error type, error tag and error status code is compared to expected
+     * values.
+     */
+    @Test
+    public void makeQNameFromIdentifierEmptyIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier("");
+            fail("Test should fail due to empty identifier");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test with identifier containing double slash. Between // there is one empty string which will be
+     * incorrectly considered to be module revision. Test is expected to fail with
+     * <code>RestconfDocumentedException</code> and error type, error tag and error status code are compared to
+     * expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierDoubleSlashNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME + "//" + TEST_MODULE_REVISION);
+            fail("Test should fail due to identifier containing double slash");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * {@link ParserIdentifier#toSchemaExportContextFromIdentifier(SchemaContext, String, DOMMountPointService)} tests
+     */
+
+    /**
+     * Positive test of getting <code>SchemaExportContext</code>. Expected module name, revision and namespace are
+     * verified.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.
+                toSchemaExportContextFromIdentifier(schemaContext, TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, null);
+
+        assertNotNull("Export context should be parsed", exportContext);
+
+        final Module module = exportContext.getModule();
+        assertNotNull("Export context should contains test module", module);
+
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, module.getName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()));
+        assertEquals("Returned not expected module namespace",
+                TEST_MODULE_NAMESPACE, module.getNamespace().toString());
+    }
+
+    /**
+     * Test of getting <code>SchemaExportContext</code> when desired module is not found.
+     * <code>SchemaExportContext</code> should be created but module should be set to <code>null</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNotFoundTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                "not-existing-module" + "/" + "2016-01-01",
+                null);
+
+        assertNotNull("Export context should be parsed", exportContext);
+        assertNull("Not-existing module should be null", exportContext.getModule());
+    }
+
+    /**
+     * Negative test trying to get <code>SchemaExportContext</code> with invalid identifier. Test is expected to fail
+     * with <code>RestconfDocumentedException</code> error type, error tag and error status code are compared to
+     * expected values.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.toSchemaExportContextFromIdentifier(
+                    schemaContext, TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME, null);
+            fail("Test should fail due to invalid identifier supplied");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Positive test of getting <code>SchemaExportContext</code> for module behind mount point.
+     * Expected module name, revision and namespace are verified.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+                mountPointService);
+
+        final Module module = exportContext.getModule();
+        assertNotNull("Export context should contains test module", module);
+
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, module.getName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()));
+        assertEquals("Returned not expected module namespace",
+                TEST_MODULE_NAMESPACE, module.getNamespace().toString());
+    }
+
+    /**
+     * Negative test of getting <code>SchemaExportContext</code> when desired module is not found behind mount point.
+     * <code>SchemaExportContext</code> should be still created but module should be set to <code>null</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + "not-existing-module" + "/" + "2016-01-01",
+                mountPointService);
+
+        assertNotNull("Export context should be parsed", exportContext);
+        assertNull("Not-existing module should be null", exportContext.getModule());
+    }
+
+    /**
+     * Negative test trying to get <code>SchemaExportContext</code> behind mount point with invalid identifier. Test is
+     * expected to fail with <code>RestconfDocumentedException</code> error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.toSchemaExportContextFromIdentifier(
+                    schemaContext,
+                    MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME,
+                    mountPointService);
+
+            fail("Test should fail due to invalid identifier supplied");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of getting <code>SchemaExportContext</code> with identifier beginning with slash defining module
+     * behind mount point. Test is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointBeginsWithSlashNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+                mountPointService);
+    }
+
+    /**
+     * Negative test of getting <code>SchemaExportContext</code> when supplied identifier is null.
+     * <code>NullPointerException</code> is expected. <code>DOMMountPointService</code> is not used.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullIdentifierNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(schemaContext, null, null);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>SchemaContext</code> is
+     * <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(null, TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, null);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>SchemaContext</code> is
+     * <code>null</code> and identifier specifies module behind mount point. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                null,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION,
+                mountPointService);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>DOMMountPointService</code>
+     * is <code>null</code> and identifier defines module behind mount point. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullMountPointServiceNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION,
+                null);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when <code>SchemaContext</code> behind mount
+     * point is <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                "/"
+                + RestconfConstants.MOUNT
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION,
+                mockMountPointService);
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/schema/context/RestconfSchemaUtilTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/schema/context/RestconfSchemaUtilTest.java
new file mode 100644 (file)
index 0000000..7fd781a
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+ * 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.utils.schema.context;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.opendaylight.restconf.Draft11.MonitoringModule;
+import static org.opendaylight.restconf.Draft11.RestconfModule;
+
+import com.google.common.collect.Sets;
+import java.util.NoSuchElementException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Unit tests for {@link RestconfSchemaUtil}
+ */
+public class RestconfSchemaUtilTest {
+    // schema with testing modules
+    private SchemaContext schemaContext;
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Before
+    public void setup() throws Exception {
+        schemaContext = TestRestconfUtils.loadSchemaContext("/modules/restconf-module-testing");
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeListModuleTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                RestconfModule.MODULE_LIST_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node "+ RestconfModule.MODULE_LIST_SCHEMA_NODE + " should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), RestconfModule.MODULE_LIST_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeListStreamTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node " + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULES_CONTAINER_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerModulesTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node " + RestconfModule.MODULES_CONTAINER_SCHEMA_NODE + "should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAMS_CONTAINER_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerStreamsTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node " + MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE + " should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module is
+     * <code>null</code>. Test is expected to fail catching <code>NullPointerException</code>.
+     */
+    @Test
+    public void getRestconfSchemaNodeNullRestconfModuleNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        RestconfSchemaUtil.getRestconfSchemaNode(null, RestconfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when name of the schema node name is
+     * <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void getRestconfSchemaNodeNullSchemaNodeNameNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule("ietf-restconf"), null);
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when name of the schema node name
+     * references to not existing node. Test is expected to fail catching code>RestconfDocumentedException</code> and
+     * checking expected error type, error tag and error status code.
+     */
+    @Test
+    public void getRestconfSchemaNodeNotExistingSchemaNodeNameNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule("ietf-restconf"), "not-existing-node");
+            fail("Test should fail due to not-existing node name");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULES_CONTAINER_SCHEMA_NODE} when this node cannot be found.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerModulesNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule(
+                    "restconf-module-with-missing-container-modules"), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to missing " + RestconfModule.MODULES_CONTAINER_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} when this node cannot be found.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeListModuleNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-list-module"),
+                    RestconfModule.MODULE_LIST_SCHEMA_NODE);
+            fail("Test should fail due to missing " + RestconfModule.MODULE_LIST_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAMS_CONTAINER_SCHEMA_NODE} when this node cannot
+     * be found. <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerStreamsNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-container-streams"),
+                    MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to missing " + MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} when this node cannot be found.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeListStreamNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-list-stream"),
+                    MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+            fail("Test should fail due to missing " + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module does not
+     * contains restconf grouping. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+     * type, error tag and error status code are compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeMissingRestconfGroupingNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-grouping-restconf"),
+                    RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to missing restconf grouping in Restconf module");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module contains
+     * restconf grouping which does not contain any child nodes. Test is expected to fail with
+     * <code>NoSuchElementException</code>.
+     */
+    @Test
+    public void getRestconfSchemaNodeEmptyRestconfGroupingNegativeTest() {
+        thrown.expect(NoSuchElementException.class);
+        RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("restconf-module-with-empty-grouping-restconf"),
+                RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+    }
+
+    /**
+     * Positive test trying to find <code>DataSchemaNode</code> of {@link RestconfModule#RESTCONF_GROUPING_SCHEMA_NODE}
+     * in Restconf module groupings collection.
+     */
+    @Test
+    public void findSchemaNodeInCollectionTest() {
+        final SchemaNode schemaNode = RestconfSchemaUtil.findSchemaNodeInCollection(
+                getTestingRestconfModule("ietf-restconf").getGroupings(),
+                RestconfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+
+        assertNotNull("Restconf grouping schema node should be found", schemaNode);
+        assertEquals("Incorrect grouping was returned",
+                schemaNode.getQName().getLocalName(), RestconfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+        assertEquals("Incorrect grouping was returned",
+                schemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect grouping was returned",
+                schemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> of not existing groupings schema node name in Restconf
+     * module grouping collection. Test is expected to fail catching <code>RestconfDocumentedException</code> and
+     * checking for correct error type, error tag and error status code.
+     */
+    @Test
+    public void findSchemaNodeInCollectionNegativeTest() {
+        try {
+            RestconfSchemaUtil.findSchemaNodeInCollection(
+                    getTestingRestconfModule("ietf-restconf").getGroupings(), "not-existing-grouping");
+            fail("Test should fail due to missing not-existing grouping in Restconf grouping collection");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> of existing schema node name in <code>null</code>
+     * collection. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void findSchemaNodeInCollectionNullCollectionNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        RestconfSchemaUtil.findSchemaNodeInCollection(null, RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> for schema node name in empty collection. Test is
+     * expected to fail with <code>RestconfDocumentedException</code>. Error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void findSchemaNodeInCollectionEmptyCollectionNegativeTest() {
+        try {
+            RestconfSchemaUtil.findSchemaNodeInCollection(
+                    Sets.newHashSet(), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to empty schema nodes collection");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> of <code>null</code> schema node name in Restconf module
+     * groupings collection. Test is expected to fail with <code>RestconfDocumentedException</code>. Error type, error
+     * tag and error status code are compared to expected values.
+     */
+    @Test
+    public void findSchemaNodeInCollectionNullSchemaNodeName() {
+        try {
+            RestconfSchemaUtil.findSchemaNodeInCollection(
+                    getTestingRestconfModule("ietf-restconf").getGroupings(), null);
+            fail("Test should fail due to null schema node name");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * There are multiple testing Restconf modules for different test cases. It is possible to distinguish them by
+     * name or by namespace. This method is looking for Restconf test module by its name.
+     * @param s Testing Restconf module name
+     * @return Restconf module
+     */
+    private Module getTestingRestconfModule(final String s) {
+        return schemaContext.findModuleByName(s, Draft11.RestconfModule.IETF_RESTCONF_QNAME.getRevision());
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-empty-grouping-restconf.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-empty-grouping-restconf.yang
new file mode 100644 (file)
index 0000000..6e8377d
--- /dev/null
@@ -0,0 +1,519 @@
+module restconf-module-with-empty-grouping-restconf {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwegr";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+     /** This grouping is empty for testing purposes  **/
+     grouping restconf {}
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-container-modules.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-container-modules.yang
new file mode 100644 (file)
index 0000000..c117ffb
--- /dev/null
@@ -0,0 +1,639 @@
+module restconf-module-with-missing-container-modules {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restcon-rmwmcmf";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+
+     grouping restconf {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          the RESTCONF API resource.";
+
+       container restconf {
+         description
+           "Conceptual container representing the
+            application/yang.api resource type.";
+
+         container config {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              unified configuration datastore containing YANG data
+              nodes. The child nodes of this container are
+              configuration data resources (application/yang.data)
+              defined as top-level YANG data nodes from the modules
+              advertised by the server in /restconf/modules.";
+         }
+
+         container operational {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              operational data supported by the server.  The child
+              nodes of this container are operational data resources
+              (application/yang.data) defined as top-level
+              YANG data nodes from the modules advertised by
+              the server in /restconf/modules.";
+         }
+
+         // removed container modules for testing purposes
+
+         container operations {
+           description
+             "Container for all operation resources
+              (application/yang.operation),
+
+              Each resource is represented as an empty leaf with the
+              name of the RPC operation from the YANG rpc statement.
+
+              E.g.;
+
+                 POST /restconf/operations/show-log-errors
+
+                 leaf show-log-errors {
+                   type empty;
+                 }
+             ";
+         }
+
+         container streams {
+           description
+             "Container representing the notification event streams
+              supported by the server.";
+            reference
+              "RFC 5277, Section 3.4, <streams> element.";
+
+           list stream {
+             key name;
+             description
+               "Each entry describes an event stream supported by
+                the server.";
+
+             leaf name {
+               type string;
+               description "The stream name";
+               reference "RFC 5277, Section 3.4, <name> element.";
+             }
+
+             leaf description {
+               type string;
+               description "Description of stream content";
+               reference
+                 "RFC 5277, Section 3.4, <description> element.";
+             }
+
+             leaf replay-support {
+               type boolean;
+               description
+                 "Indicates if replay buffer supported for this stream";
+               reference
+                 "RFC 5277, Section 3.4, <replaySupport> element.";
+             }
+
+             leaf replay-log-creation-time {
+               type yang:date-and-time;
+               description
+                 "Indicates the time the replay log for this stream
+                  was created.";
+               reference
+                 "RFC 5277, Section 3.4, <replayLogCreationTime>
+                  element.";
+             }
+
+             leaf events {
+               type empty;
+               description
+                 "Represents the entry point for establishing
+                  notification delivery via server sent events.";
+             }
+           }
+         }
+
+         leaf version {
+           type enumeration {
+             enum "1.0" {
+               description
+                 "Version 1.0 of the RESTCONF protocol.";
+             }
+           }
+           config false;
+           description
+             "Contains the RESTCONF protocol version.";
+         }
+       }
+     }  // grouping restconf
+
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-grouping-restconf.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-grouping-restconf.yang
new file mode 100644 (file)
index 0000000..0e8009a
--- /dev/null
@@ -0,0 +1,518 @@
+module restconf-module-with-missing-grouping-restconf {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwmgr";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+     /** grouping restconf removed for testing purposes **/
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-module.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-module.yang
new file mode 100644 (file)
index 0000000..34b4d48
--- /dev/null
@@ -0,0 +1,650 @@
+module restconf-module-with-missing-list-module {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwmlm";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+
+     grouping restconf {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          the RESTCONF API resource.";
+
+       container restconf {
+         description
+           "Conceptual container representing the
+            application/yang.api resource type.";
+
+         container config {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              unified configuration datastore containing YANG data
+              nodes. The child nodes of this container are
+              configuration data resources (application/yang.data)
+              defined as top-level YANG data nodes from the modules
+              advertised by the server in /restconf/modules.";
+         }
+
+         container operational {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              operational data supported by the server.  The child
+              nodes of this container are operational data resources
+              (application/yang.data) defined as top-level
+              YANG data nodes from the modules advertised by
+              the server in /restconf/modules.";
+         }
+
+         container modules {
+           description
+             "Contains a list of module description entries.
+              These modules are currently loaded into the server.";
+
+            // removed list module for testing purposes + added list test-list
+            list test-list {
+              leaf test-leaf {
+                type string;
+              }
+            }
+         }
+
+         container operations {
+           description
+             "Container for all operation resources
+              (application/yang.operation),
+
+              Each resource is represented as an empty leaf with the
+              name of the RPC operation from the YANG rpc statement.
+
+              E.g.;
+
+                 POST /restconf/operations/show-log-errors
+
+                 leaf show-log-errors {
+                   type empty;
+                 }
+             ";
+         }
+
+         container streams {
+           description
+             "Container representing the notification event streams
+              supported by the server.";
+            reference
+              "RFC 5277, Section 3.4, <streams> element.";
+
+           list stream {
+             key name;
+             description
+               "Each entry describes an event stream supported by
+                the server.";
+
+             leaf name {
+               type string;
+               description "The stream name";
+               reference "RFC 5277, Section 3.4, <name> element.";
+             }
+
+             leaf description {
+               type string;
+               description "Description of stream content";
+               reference
+                 "RFC 5277, Section 3.4, <description> element.";
+             }
+
+             leaf replay-support {
+               type boolean;
+               description
+                 "Indicates if replay buffer supported for this stream";
+               reference
+                 "RFC 5277, Section 3.4, <replaySupport> element.";
+             }
+
+             leaf replay-log-creation-time {
+               type yang:date-and-time;
+               description
+                 "Indicates the time the replay log for this stream
+                  was created.";
+               reference
+                 "RFC 5277, Section 3.4, <replayLogCreationTime>
+                  element.";
+             }
+
+             leaf events {
+               type empty;
+               description
+                 "Represents the entry point for establishing
+                  notification delivery via server sent events.";
+             }
+           }
+         }
+
+         leaf version {
+           type enumeration {
+             enum "1.0" {
+               description
+                 "Version 1.0 of the RESTCONF protocol.";
+             }
+           }
+           config false;
+           description
+             "Contains the RESTCONF protocol version.";
+         }
+       }
+     }  // grouping restconf
+
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
index fcc7e308a1e9d8a629e7a8896893fae44805cdf7..ba8c9aa1d7f7f8e6d054053c6a0e789f588faa5f 100644 (file)
@@ -586,7 +586,12 @@ module restconf-module-with-missing-list-stream {
             reference
               "RFC 5277, Section 3.4, <streams> element.";
 
-           /** deleted list stream for testing purposes **/
+           /** deleted list stream for testing purposes + added list test-list **/
+           list test-list {
+             leaf test-leaf {
+               type string;
+             }
+           }
          }
 
          leaf version {
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/mount-point.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/mount-point.yang
new file mode 100644 (file)
index 0000000..02a468f
--- /dev/null
@@ -0,0 +1,17 @@
+module mount-point {
+  namespace "mount:point";
+  prefix "mountp";
+  yang-version 1;
+
+  import parser-identifier-included { prefix pii; revision-date 2016-06-02; }
+
+  revision 2016-06-02 {
+    description "Initial revision.";
+  }
+
+  container mount-container {
+    leaf point-number {
+      type uint8;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test-included.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test-included.yang
new file mode 100644 (file)
index 0000000..d5200a9
--- /dev/null
@@ -0,0 +1,23 @@
+module parser-identifier-included {
+  namespace "parser:identifier:included";
+  prefix "parseridinc";
+  yang-version 1;
+
+  revision 2016-06-02 {
+    description
+      "Initial revision.";
+  }
+
+  list list-1 {
+    key "name revision";
+    description "List in grouping";
+
+    leaf name {
+      type string;
+    }
+
+    leaf revision {
+      type string;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test.yang
new file mode 100644 (file)
index 0000000..ac91dc1
--- /dev/null
@@ -0,0 +1,40 @@
+module parser-identifier {
+  namespace "parser:identifier";
+  prefix "parserid";
+  yang-version 1;
+
+  import parser-identifier-included { prefix pii; revision-date 2016-06-02; }
+
+  revision 2016-06-02 {
+    description
+      "Initial revision.";
+  }
+
+  container cont1 {
+    container cont2 {
+      list listTest {
+        uses group;
+      }
+    }
+  }
+
+  grouping group {
+    list list-in-grouping {
+      key name;
+
+      leaf name {
+        type string;
+      }
+
+      leaf leaf-A.B {
+        type uint8;
+      }
+    }
+  }
+
+  augment "/pii:list-1" {
+    leaf augment-leaf {
+      type string;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/test-module.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/test-module.yang
new file mode 100644 (file)
index 0000000..a9a6756
--- /dev/null
@@ -0,0 +1,9 @@
+module test-module {
+  namespace "test:module";
+  prefix "testm";
+  yang-version 1;
+
+  revision 2016-06-02 {
+    description "Initial revision.";
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test-included.yang b/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test-included.yang
new file mode 100644 (file)
index 0000000..b46898c
--- /dev/null
@@ -0,0 +1,22 @@
+module deserializer-test-included {
+  namespace "deserializer:test:included";
+  prefix "dti";
+  yang-version 1;
+
+  revision 2016-06-06 {
+    description
+      "Initial revision.";
+  }
+
+  list augmented-list {
+    key list-key;
+
+    leaf list-key {
+      type uint16;
+    }
+
+    leaf list-value {
+      type string;
+    }
+  }
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test.yang b/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test.yang
new file mode 100644 (file)
index 0000000..38f989e
--- /dev/null
@@ -0,0 +1,90 @@
+module deserializer-test {
+  namespace "deserializer:test";
+  prefix "dt";
+  yang-version 1;
+
+  import deserializer-test-included { prefix dti; revision-date 2016-06-06; }
+
+  revision 2016-06-06 {
+    description
+      "Initial revision.";
+  }
+
+  container contA {
+    leaf-list leaf-list-A {
+      type string;
+    }
+
+    leaf leaf-A {
+      type string;
+    }
+
+    list list-A {
+      key list-key;
+
+      leaf list-key {
+        type uint8;
+      }
+
+      leaf-list leaf-list-AA {
+        type string;
+      }
+    }
+  }
+
+  leaf-list leaf-list-0 {
+    type boolean;
+  }
+
+  leaf leaf-0 {
+    type string;
+  }
+
+  list list-no-key {
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+  }
+
+  list list-one-key {
+    key name;
+
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+  }
+
+  list list-multiple-keys {
+    key "name number enabled";
+
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+
+    leaf enabled {
+      type boolean;
+    }
+
+    leaf string-value {
+      type string;
+    }
+  }
+
+  augment "/dti:augmented-list" {
+    leaf augmented-leaf {
+      type string;
+    }
+  }
+}