Device config synchronizer

- initial sketch of Device Config Synchronizer outline (ONOS-6745)

Change-Id: I57c8ab6c3511f12c15e3501aa61498eb18264b27
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
new file mode 100644
index 0000000..78812fb
--- /dev/null
+++ b/apps/configsync/src/test/java/org/onosproject/d/config/sync/impl/DynamicDeviceConfigSynchronizerTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.d.config.sync.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.config.DynamicConfigEvent;
+import org.onosproject.config.DynamicConfigServiceAdapter;
+import org.onosproject.config.Filter;
+import org.onosproject.d.config.DeviceResourceIds;
+import org.onosproject.d.config.ResourceIds;
+import org.onosproject.d.config.sync.DeviceConfigSynchronizationProvider;
+import org.onosproject.d.config.sync.operation.SetRequest;
+import org.onosproject.d.config.sync.operation.SetRequest.Change;
+import org.onosproject.d.config.sync.operation.SetRequest.Change.Operation;
+import org.onosproject.d.config.sync.operation.SetResponse;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.NetworkConfigServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.yang.model.DataNode;
+import org.onosproject.yang.model.InnerNode;
+import org.onosproject.yang.model.LeafNode;
+import org.onosproject.yang.model.ResourceId;
+
+import com.google.common.collect.Iterables;
+
+public class DynamicDeviceConfigSynchronizerTest {
+
+    static final String TEST_NS = "testNS";
+
+    static final ResourceId REL_INTERFACES = ResourceId.builder()
+                .addBranchPointSchema("interfaces", TEST_NS)
+                .build();
+
+    static final DeviceId DID = DeviceId.deviceId("test:device1");
+
+    DynamicDeviceConfigSynchronizer sut;
+
+    TestDynamicConfigService dyConService;
+
+    CountDownLatch providerCalled = new CountDownLatch(1);
+
+    BiFunction<ResourceId, Filter, DataNode> onDcsRead;
+
+    BiFunction<DeviceId, SetRequest, CompletableFuture<SetResponse>> onSetConfiguration;
+
+    @Before
+    public void setUp() throws Exception {
+
+        sut = new DynamicDeviceConfigSynchronizer();
+        dyConService = new TestDynamicConfigService();
+        sut.dynConfigService = dyConService;
+        sut.netcfgService = new NetworkConfigServiceAdapter();
+
+        sut.activate();
+
+        sut.register(new MockDeviceConfigSynchronizerProvider());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        sut.deactivate();
+    }
+
+    @Test
+    public void testDispatchRequest() throws Exception {
+
+        ResourceId devicePath = DeviceResourceIds.toResourceId(DID);
+        ResourceId cfgPath = REL_INTERFACES;
+        ResourceId absPath = ResourceIds.concat(devicePath, cfgPath);
+        DynamicConfigEvent event = new DynamicConfigEvent(DynamicConfigEvent.Type.NODE_REPLACED, absPath);
+
+        // assertions
+        onDcsRead = (path, filter) -> {
+            assertTrue(filter.isEmptyFilter());
+            assertEquals("DCS get access by absolute RID", absPath, path);
+            return deviceConfigNode();
+        };
+
+        onSetConfiguration = (deviceId, request) -> {
+            assertEquals(DID, deviceId);
+            assertEquals(1, request.changes().size());
+            Change change = Iterables.get(request.changes(), 0);
+            assertEquals("Provider get access by rel RID", REL_INTERFACES, change.path());
+            assertEquals(Operation.REPLACE, change.op());
+            assertEquals("interfaces", change.val().key().schemaId().name());
+            // walk and test children if it adds value
+
+            providerCalled.countDown();
+            return CompletableFuture.completedFuture(SetResponse.ok(request));
+        };
+
+        // start test run
+
+        // imitate event from DCS
+        dyConService.postEvent(event);
+
+        // assert that it reached the provider
+        providerCalled.await(5, TimeUnit.HOURS);
+    }
+
+    /**
+     * DataNode for testing.
+     *
+     * <pre>
+     *   +-interfaces
+     *      |
+     *      +- interface{intf-name="en0"}
+     *           |
+     *           +- speed = "10G"
+     *           +- state = "up"
+     *
+     * </pre>
+     * @return DataNode
+     */
+    private DataNode deviceConfigNode() {
+        InnerNode.Builder intfs = InnerNode.builder("interfaces", TEST_NS);
+        intfs.type(DataNode.Type.SINGLE_INSTANCE_NODE);
+        InnerNode.Builder intf = intfs.createChildBuilder("interface", TEST_NS);
+        intf.type(DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE);
+        intf.addKeyLeaf("name", TEST_NS, "Ethernet0/0");
+        LeafNode.Builder speed = intf.createChildBuilder("mtu", TEST_NS, "1500");
+        speed.type(DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE);
+
+        intf.addNode(speed.build());
+        intfs.addNode(intf.build());
+        return intfs.build();
+    }
+
+    private class TestDynamicConfigService extends DynamicConfigServiceAdapter {
+
+        public void postEvent(DynamicConfigEvent event) {
+            listenerRegistry.process(event);
+        }
+
+        @Override
+        public DataNode readNode(ResourceId path, Filter filter) {
+            return onDcsRead.apply(path, filter);
+        }
+    }
+
+    private class MockDeviceConfigSynchronizerProvider
+            implements DeviceConfigSynchronizationProvider {
+
+        @Override
+        public ProviderId id() {
+            return new ProviderId(DID.uri().getScheme(), "test-provider");
+        }
+
+        @Override
+        public CompletableFuture<SetResponse> setConfiguration(DeviceId deviceId,
+                                                               SetRequest request) {
+            return onSetConfiguration.apply(deviceId, request);
+        }
+    }
+
+}