ONOS-7007 dealing with root relative path

- workarounds to deal with root relative ResourceId used by DynamicConfigEvent
- fix issue, where duplicate KeyLeaf was getting added

Change-Id: I957044f8da3d71e064663011c8bd8fceeb1cf44e
diff --git a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
index dc93e58..bb909bd 100644
--- a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
+++ b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.config;
 
+import java.util.Arrays;
 import java.util.Iterator;
 
 import java.util.LinkedList;
@@ -25,6 +26,8 @@
 import org.onosproject.yang.model.ListKey;
 import org.onosproject.yang.model.NodeKey;
 import org.onosproject.yang.model.ResourceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
 
@@ -43,6 +46,8 @@
 @Beta
 public final class ResourceIdParser {
 
+    private static final Logger log = LoggerFactory.getLogger(ResourceIdParser.class);
+
     /**
      * root node name.
      */
@@ -117,10 +122,14 @@
         return (path + leaf.substring(leaf.indexOf(KEY_SEP)));
     }
 
+    // 1.12.0 - not used by anyone
+    @Deprecated
     public static String appendKeyLeaf(String path, String key) {
         return (path + EL_SEP + key);
     }
 
+    // 1.12.0 - not used by anyone
+    @Deprecated
     public static String appendKeyLeaf(String path, KeyLeaf key) {
         StringBuilder bldr = new StringBuilder();
         bldr.append(key.leafSchema().name());
@@ -161,6 +170,8 @@
         return (path + bldr.toString());
     }
 
+    // 1.12.0 - not used by anyone
+    @Deprecated
     public static String parseNodeKey(NodeKey key) {
         if (key == null) {
             return null;
@@ -269,6 +280,7 @@
         while (itr.hasNext()) {
             String name = itr.next();
             if (name.contains(VAL_SEP)) {
+                // dead branch? VAL_SEP never used in parseResId
                 resBldr.addLeafListBranchPoint(name.substring(0, name.indexOf(NM_SEP)),
                         name.substring(name.indexOf(NM_SEP) + 1, name.indexOf(VAL_SEP)),
                         name.substring(name.indexOf(VAL_SEP) + 1));
@@ -282,7 +294,15 @@
                     if (el.length != 3) {
                         throw new FailedException("Malformed event subject, cannot parse");
                     }
+                    try {
                     resBldr.addKeyLeaf(el[0], el[1], el[2]);
+                    } catch (Exception e) {
+                        log.error("dpath={}", dpath);
+                        log.error("name={}", name);
+                        log.error("key={}", key);
+                        log.error("el={}", Arrays.asList(el));
+                        throw e;
+                    }
                 }
             } else {
                 resBldr.addBranchPointSchema(name.substring(0, name.indexOf(NM_SEP)),
diff --git a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
index 97731a6..1af7b34 100644
--- a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
+++ b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
@@ -167,7 +167,6 @@
         if (node.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
             addLeaf(path, (LeafNode) node);
         } else if (node.type() == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE) {
-            path = ResourceIdParser.appendLeafList(path, (LeafListKey) node.key());
             if (completeVersioned(keystore.get(DocumentPath.from(path))) != null) {
                 throw new FailedException("Requested node already present in the" +
                                                   " store, please use an update method");
@@ -176,7 +175,6 @@
         } else if (node.type() == DataNode.Type.SINGLE_INSTANCE_NODE) {
             traverseInner(path, (InnerNode) node);
         } else if (node.type() == DataNode.Type.MULTI_INSTANCE_NODE) {
-            path = ResourceIdParser.appendKeyList(path, (ListKey) node.key());
             if (completeVersioned(keystore.get(DocumentPath.from(path))) != null) {
                 throw new FailedException("Requested node already present in the" +
                                                   " store, please use an update method");
diff --git a/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java b/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java
index 605ae63..358b400 100644
--- a/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java
+++ b/apps/config/src/main/java/org/onosproject/d/config/DeviceResourceIds.java
@@ -23,6 +23,7 @@
 import org.onosproject.yang.model.ListKey;
 import org.onosproject.yang.model.NodeKey;
 import org.onosproject.yang.model.ResourceId;
+import org.onosproject.yang.model.SchemaId;
 
 import com.google.common.annotations.Beta;
 
@@ -38,7 +39,6 @@
     //         +- device (=device root node:ListKey)
 
 
-    // FIXME randomly defined namespace, replace with something appropriate
     /**
      * SchemaId namespace for DCS defined nodes.
      */
@@ -52,22 +52,27 @@
      * SchemaId name for devices node.
      */
     public static final String DEVICES_NAME = "devices";
+    public static final SchemaId DEVICES_SCHEMA = new SchemaId(DEVICES_NAME, DCS_NAMESPACE);
     /**
      * SchemaId name for device node.
      */
     public static final String DEVICE_NAME = "device";
+    public static final SchemaId DEVICE_SCHEMA = new SchemaId(DEVICE_NAME, DCS_NAMESPACE);
     /**
      * KeyLeaf {@code name}, which holds DeviceId information on device node.
      */
     public static final String DEVICE_ID_KL_NAME = "device-id";
 
     /**
-     * ResourceId pointing at root node.
+     * Absolute ResourceId pointing at root node.
+     * @deprecated Use {@link ResourceIds#ROOT_ID} instead
      */
-    public static final ResourceId ROOT_ID = ResourceId.builder()
-            .addBranchPointSchema(ROOT_NAME, DCS_NAMESPACE)
-            .build();
+    @Deprecated
+    public static final ResourceId ROOT_ID = ResourceIds.ROOT_ID;
 
+    /**
+     * Absolute ResourceId pointing at 'devices' node.
+     */
     public static final ResourceId DEVICES_ID = ResourceId.builder()
             .addBranchPointSchema(ROOT_NAME, DCS_NAMESPACE)
             .addBranchPointSchema(DEVICES_NAME, DCS_NAMESPACE)
@@ -80,42 +85,59 @@
     /**
      * nodeKeys index for root node.
      */
+    @Deprecated
     static final int ROOT_INDEX = 0;
     /**
-     * nodeKeys index for devices node.
+     * nodeKeys index relative from root for devices node.
      */
     static final int DEVICES_INDEX = 1;
     /**
-     * nodeKeys index for device node.
+     * nodeKeys index relative from root for device node.
      */
     static final int DEVICE_INDEX = 2;
 
     /**
+     * Converts root relative ResourceId used by DynamicConfigEvent.
+     *
+     * @param rootRelative resource Id
+     * @return absolute ResourceId.
+     */
+    @Beta
+    public static ResourceId toAbsolute(ResourceId rootRelative) {
+        if (ResourceIds.startsWithRootNode(rootRelative)) {
+            return rootRelative;
+        }
+        return ResourceIds.concat(ResourceIds.ROOT_ID, rootRelative);
+    }
+
+    /**
      * Tests if specified path points to root node of a Device.
      *
      * @param path to test.
      * @return true if path points to root node of a Device.
      */
     public static boolean isDeviceRootNode(ResourceId path) {
-        return path.nodeKeys().size() == 3 &&
-               isUnderDeviceRootNode(path);
+        if (ResourceIds.startsWithRootNode(path)) {
+            return path.nodeKeys().size() == 3 &&
+                    isUnderDeviceRootNode(path);
+        } else {
+            return path.nodeKeys().size() == 2 &&
+                    isUnderDeviceRootNode(path);
+        }
     }
 
     /**
-     * Tests if specified path points to root node of a Device.
+     * Tests if specified path points to root node of a Device or it's subtree.
      *
      * @param path to test.
      * @return true if path points to root node of a Device.
      */
     public static boolean isUnderDeviceRootNode(ResourceId path) {
-        return path.nodeKeys().size() >= 3 &&
-                // TODO Would be better to test whole schemeId
-                DEVICE_NAME.equals(path.nodeKeys().get(DEVICE_INDEX).schemaId().name()) &&
-                (path.nodeKeys().get(DEVICE_INDEX) instanceof ListKey) &&
-                // TODO Would be better to test whole schemeId
-                DEVICES_NAME.equals(path.nodeKeys().get(DEVICES_INDEX).schemaId().name()) &&
-                ROOT_NODE.equals(path.nodeKeys().get(ROOT_INDEX));
-    }
+        int rootIdx = ResourceIds.startsWithRootNode(path) ? 0 : -1;
+        return path.nodeKeys().size() >= rootIdx + 3 &&
+                DEVICE_SCHEMA.equals(path.nodeKeys().get(rootIdx + DEVICE_INDEX).schemaId()) &&
+                (path.nodeKeys().get(rootIdx + DEVICE_INDEX) instanceof ListKey) &&
+                DEVICES_SCHEMA.equals(path.nodeKeys().get(rootIdx + DEVICES_INDEX).schemaId());    }
 
     /**
      * Tests if specified path points to root or devices node.
@@ -129,10 +151,9 @@
     }
 
     public static boolean isDevicesNode(ResourceId path) {
-        return path.nodeKeys().size() == 2 &&
-                // TODO Would be better to test whole schemeId
-                DEVICES_NAME.equals(path.nodeKeys().get(DEVICES_INDEX).schemaId().name()) &&
-                ROOT_NODE.equals(path.nodeKeys().get(ROOT_INDEX));
+        int rootIdx = ResourceIds.startsWithRootNode(path) ? 0 : -1;
+        return path.nodeKeys().size() == rootIdx + 2 &&
+                DEVICES_SCHEMA.equals(path.nodeKeys().get(rootIdx + DEVICES_INDEX).schemaId());
     }
 
     public static boolean isRootNode(ResourceId path) {
@@ -151,7 +172,8 @@
         checkArgument(isUnderDeviceRootNode(path), path);
         // FIXME if we decide to drop any of intermediate nodes
         //        "/" - "devices" - "device"
-        return toDeviceId(path.nodeKeys().get(DEVICE_INDEX));
+        int rootIdx = ResourceIds.startsWithRootNode(path) ? 0 : -1;
+        return toDeviceId(path.nodeKeys().get(rootIdx + DEVICE_INDEX));
     }
 
     /**
@@ -162,8 +184,7 @@
      * @throws IllegalArgumentException if not a device node
      */
     public static DeviceId toDeviceId(NodeKey<?> deviceRoot) {
-        // TODO Would be better to test whole schemeId
-        if (!DEVICE_NAME.equals(deviceRoot.schemaId().name())) {
+        if (!DEVICE_SCHEMA.equals(deviceRoot.schemaId())) {
             throw new IllegalArgumentException(deviceRoot + " is not a device node");
         }
 
@@ -189,7 +210,7 @@
      * Transforms DeviceId into a ResourceId pointing to device root node.
      *
      * @param deviceId to transform
-     * @return ResourceId
+     * @return absolute ResourceId
      */
     public static ResourceId toResourceId(DeviceId deviceId) {
         return ResourceId.builder()
diff --git a/apps/config/src/main/java/org/onosproject/d/config/DynamicDeviceConfigServiceView.java b/apps/config/src/main/java/org/onosproject/d/config/DynamicDeviceConfigServiceView.java
index 1ec6929..c3f3e7b 100644
--- a/apps/config/src/main/java/org/onosproject/d/config/DynamicDeviceConfigServiceView.java
+++ b/apps/config/src/main/java/org/onosproject/d/config/DynamicDeviceConfigServiceView.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.d.config.ResourceIds.ROOT_ID;
 import static org.slf4j.LoggerFactory.getLogger;
 
 import java.util.Collections;
@@ -51,6 +52,7 @@
 
     private final DeviceId deviceId;
 
+    // absolute ResourceId
     private ResourceId deviceRoot;
 
     /**
@@ -136,10 +138,16 @@
     }
 
     private ResourceId toDeviceRelativeId(ResourceId path) {
-        checkArgument(path.nodeKeys().contains(DeviceResourceIds.ROOT_NODE),
-                      "%s was not absolute path", path);
-
-        return ResourceIds.relativize(deviceRoot, path);
+        // case: absolute
+        if (ResourceIds.startsWithRootNode(path)) {
+            return ResourceIds.relativize(deviceRoot, path);
+        }
+        // case: root relative
+        if (DeviceResourceIds.isUnderDeviceRootNode(path)) {
+            //                                        TODO not efficient
+            return ResourceIds.relativize(deviceRoot, ResourceIds.concat(ROOT_ID, path));
+        }
+        throw new IllegalArgumentException(path + " was not absolute device path");
     }
 
     class DynamicDeviceConfigListener implements DynamicConfigListener {
diff --git a/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java b/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java
index fc875a9..a08c04a 100644
--- a/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java
+++ b/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java
@@ -18,10 +18,14 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.slf4j.LoggerFactory.getLogger;
 
+import java.util.Iterator;
+import java.util.Objects;
+
 import org.onosproject.yang.model.DataNode;
 import org.onosproject.yang.model.KeyLeaf;
 import org.onosproject.yang.model.LeafListKey;
 import org.onosproject.yang.model.ListKey;
+import org.onosproject.yang.model.NodeKey;
 import org.onosproject.yang.model.ResourceId;
 import org.onosproject.yang.model.SchemaId;
 import org.slf4j.Logger;
@@ -37,6 +41,14 @@
     private static final Logger log = getLogger(ResourceIds.class);
 
     /**
+     * Absolute ResourceId pointing at root node.
+     */
+    public static final ResourceId ROOT_ID = ResourceId.builder()
+            .addBranchPointSchema(DeviceResourceIds.ROOT_NAME,
+                                  DeviceResourceIds.DCS_NAMESPACE)
+            .build();
+
+    /**
      * Builds the ResourceId of specified {@code node}.
      *
      * @param parent ResourceId of {@code node} parent
@@ -113,8 +125,20 @@
     public static ResourceId relativize(ResourceId base, ResourceId child) {
         checkArgument(child.nodeKeys().size() >= base.nodeKeys().size(),
                       "%s path must be deeper than base prefix %s", child, base);
-        checkArgument(base.nodeKeys().equals(child.nodeKeys().subList(0, base.nodeKeys().size())),
-                      "%s is not a prefix of %s", child, base);
+        @SuppressWarnings("rawtypes")
+        Iterator<NodeKey> bIt = base.nodeKeys().iterator();
+        @SuppressWarnings("rawtypes")
+        Iterator<NodeKey> cIt = child.nodeKeys().iterator();
+        while (bIt.hasNext()) {
+            NodeKey<?> b = bIt.next();
+            NodeKey<?> c = cIt.next();
+
+            checkArgument(Objects.equals(b, c),
+                          "%s is not a prefix of %s.\n" +
+                          "b:%s != c:%s",
+                          base, child,
+                          b, c);
+        }
 
         return ResourceId.builder().append(child.nodeKeys().subList(base.nodeKeys().size(),
                                                                     child.nodeKeys().size())).build();
@@ -133,4 +157,9 @@
                prefix.nodeKeys().equals(child.nodeKeys().subList(0, prefix.nodeKeys().size()));
     }
 
+    public static boolean startsWithRootNode(ResourceId path) {
+        return !path.nodeKeys().isEmpty() &&
+                DeviceResourceIds.ROOT_NAME.equals(path.nodeKeys().get(0).schemaId().name());
+    }
+
 }
diff --git a/apps/config/src/test/java/org/onosproject/d/config/DeviceResourceIdsTest.java b/apps/config/src/test/java/org/onosproject/d/config/DeviceResourceIdsTest.java
index 83baf5a..5a497d6 100644
--- a/apps/config/src/test/java/org/onosproject/d/config/DeviceResourceIdsTest.java
+++ b/apps/config/src/test/java/org/onosproject/d/config/DeviceResourceIdsTest.java
@@ -15,19 +15,29 @@
  */
 package org.onosproject.d.config;
 
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.*;
 import static org.onosproject.d.config.DeviceResourceIds.DCS_NAMESPACE;
 import static org.onosproject.d.config.DeviceResourceIds.DEVICES_NAME;
+import static org.onosproject.d.config.DeviceResourceIds.DEVICE_ID_KL_NAME;
+import static org.onosproject.d.config.DeviceResourceIds.DEVICE_NAME;
 import static org.onosproject.d.config.DeviceResourceIds.ROOT_NAME;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.onosproject.net.DeviceId;
+import org.onosproject.yang.model.KeyLeaf;
+import org.onosproject.yang.model.ListKey;
+import org.onosproject.yang.model.NodeKey;
 import org.onosproject.yang.model.ResourceId;
+import org.onosproject.yang.model.SchemaId;
 
 public class DeviceResourceIdsTest {
 
-    static final DeviceId DID_A = DeviceId.deviceId("test:A");
+    static final DeviceId DID_A = DeviceId.deviceId("test:a");
 
     static final ResourceId DEVICES = ResourceId.builder()
             .addBranchPointSchema(ROOT_NAME, DCS_NAMESPACE)
@@ -52,6 +62,12 @@
                     .build();
 
         assertEquals(DID_A, DeviceResourceIds.toDeviceId(ridAchild));
+
+        NodeKey<?> nodeKey = ridA.nodeKeys().get(2);
+        assertThat(nodeKey, is(instanceOf(ListKey.class)));
+        assertThat(nodeKey.schemaId(), is(equalTo(new SchemaId(DEVICE_NAME, DCS_NAMESPACE))));
+        ListKey listKey = (ListKey) nodeKey;
+        assertThat(listKey.keyLeafs(), is(contains(new KeyLeaf(DEVICE_ID_KL_NAME, DCS_NAMESPACE, DID_A.toString()))));
     }
 
     @Test
@@ -61,4 +77,36 @@
         assertFalse(DeviceResourceIds.isRootOrDevicesNode(ridA));
     }
 
+    @Test
+    public void testDeviceSubtreeTest() {
+        ResourceId absDevice = ResourceId.builder()
+                    .addBranchPointSchema(ROOT_NAME, DCS_NAMESPACE)
+                    .addBranchPointSchema(DEVICES_NAME, DCS_NAMESPACE)
+                    .addBranchPointSchema(DEVICE_NAME, DCS_NAMESPACE)
+                    .addKeyLeaf(DEVICE_ID_KL_NAME, DCS_NAMESPACE, DID_A.toString())
+                    .build();
+
+        NodeKey<?> deviceKey = absDevice.nodeKeys().get(2);
+        assertThat(deviceKey, is(instanceOf(ListKey.class)));
+        assertThat(deviceKey.schemaId().namespace(), is(equalTo(DCS_NAMESPACE)));
+        assertThat(deviceKey.schemaId().name(), is(equalTo(DEVICE_NAME)));
+        assertTrue(DeviceResourceIds.isUnderDeviceRootNode(absDevice));
+    }
+
+    @Test
+    public void testDeviceSubtreeEventTest() {
+        // root relative ResourceId used by DynamicConfigEvent
+        ResourceId evtDevice = ResourceId.builder()
+                    .addBranchPointSchema(DEVICES_NAME, DCS_NAMESPACE)
+                    .addBranchPointSchema(DEVICE_NAME, DCS_NAMESPACE)
+                    .addKeyLeaf(DEVICE_ID_KL_NAME, DCS_NAMESPACE, DID_A.toString())
+                    .build();
+
+        NodeKey<?> deviceKey = evtDevice.nodeKeys().get(1);
+        assertThat(deviceKey, is(instanceOf(ListKey.class)));
+        assertThat(deviceKey.schemaId().namespace(), is(equalTo(DCS_NAMESPACE)));
+        assertThat(deviceKey.schemaId().name(), is(equalTo(DEVICE_NAME)));
+        assertTrue(DeviceResourceIds.isUnderDeviceRootNode(evtDevice));
+    }
+
 }
diff --git a/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java b/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java
index 73f682c..b1c237b 100644
--- a/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java
+++ b/apps/config/src/test/java/org/onosproject/d/config/ResourceIdsTest.java
@@ -35,12 +35,12 @@
                                   DCS_NAMESPACE)
             .build();
 
-        assertEquals(DEVICES, ResourceIds.concat(DeviceResourceIds.ROOT_ID, devices));
+        assertEquals(DEVICES, ResourceIds.concat(ResourceIds.ROOT_ID, devices));
     }
 
     @Test
     public void testRelativize() {
-        ResourceId relDevices = ResourceIds.relativize(DeviceResourceIds.ROOT_ID, DEVICES);
+        ResourceId relDevices = ResourceIds.relativize(ResourceIds.ROOT_ID, DEVICES);
         assertEquals(DeviceResourceIds.DEVICES_NAME,
                      relDevices.nodeKeys().get(0).schemaId().name());
         assertEquals(DCS_NAMESPACE,
diff --git a/apps/configsync/src/main/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizer.java b/apps/configsync/src/main/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizer.java
index e0067d6..f9fdfb7 100644
--- a/apps/configsync/src/main/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizer.java
+++ b/apps/configsync/src/main/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizer.java
@@ -169,8 +169,9 @@
             DeviceId deviceId = DeviceResourceIds.toDeviceId(path);
             ResourceId deviceRootPath = DeviceResourceIds.toResourceId(deviceId);
 
-            ResourceId relPath = ResourceIds.relativize(deviceRootPath, path);
-            // FIXME figure out how to express give me everything Filter
+            ResourceId absPath = ResourceIds.concat(ResourceIds.ROOT_ID, path);
+            ResourceId relPath = ResourceIds.relativize(deviceRootPath, absPath);
+            // give me everything Filter
             Filter giveMeEverything = Filter.builder().build();
 
             DataNode node = dynConfigService.readNode(path, giveMeEverything);
diff --git a/apps/configsync/src/test/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizerTest.java b/apps/configsync/src/test/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizerTest.java
index 78812fb..bfdead6 100644
--- a/apps/configsync/src/test/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizerTest.java
+++ b/apps/configsync/src/test/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizerTest.java
@@ -16,6 +16,7 @@
 package org.onosproject.d.config.sync.impl;
 
 import static org.junit.Assert.*;
+import static org.onosproject.d.config.ResourceIds.ROOT_ID;
 
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
@@ -61,6 +62,9 @@
 
     CountDownLatch providerCalled = new CountDownLatch(1);
 
+    /**
+     * DynamicConfigService.readNode(ResourceId, Filter) stub.
+     */
     BiFunction<ResourceId, Filter, DataNode> onDcsRead;
 
     BiFunction<DeviceId, SetRequest, CompletableFuture<SetResponse>> onSetConfiguration;
@@ -89,12 +93,13 @@
         ResourceId devicePath = DeviceResourceIds.toResourceId(DID);
         ResourceId cfgPath = REL_INTERFACES;
         ResourceId absPath = ResourceIds.concat(devicePath, cfgPath);
-        DynamicConfigEvent event = new DynamicConfigEvent(DynamicConfigEvent.Type.NODE_REPLACED, absPath);
+        ResourceId evtPath = ResourceIds.relativize(ROOT_ID, absPath);
+        DynamicConfigEvent event = new DynamicConfigEvent(DynamicConfigEvent.Type.NODE_REPLACED, evtPath);
 
         // assertions
         onDcsRead = (path, filter) -> {
             assertTrue(filter.isEmptyFilter());
-            assertEquals("DCS get access by absolute RID", absPath, path);
+            assertEquals("DCService get access by root relative RID", evtPath, path);
             return deviceConfigNode();
         };