Adding routed RPC support in Remote RPC Router
[controller.git] / opendaylight / md-sal / remoterpc-routingtable / implementation / src / test / java / org / opendaylight / controller / sal / connector / remoterpc / impl / RoutingTableImplTest.java
index 50460d4e5ec897892a9a507a1b48244098887c86..0987df595689176b2d734da6c048ef00a909b2b0 100644 (file)
@@ -10,196 +10,321 @@ package org.opendaylight.controller.sal.connector.remoterpc.impl;
 
 import junit.framework.Assert;
 import org.apache.felix.dm.Component;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
 import org.opendaylight.controller.clustering.services.IClusterServices;
 import org.opendaylight.controller.sal.connector.api.RpcRouter;
-import org.opendaylight.controller.sal.connector.remoterpc.api.RouteChangeListener;
+import org.opendaylight.controller.sal.connector.remoterpc.api.RoutingTable;
+import org.opendaylight.controller.sal.connector.remoterpc.api.RoutingTableException;
+import org.opendaylight.controller.sal.connector.remoterpc.api.SystemException;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
 
 import java.net.URI;
 import java.util.EnumSet;
 import java.util.HashSet;
-import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.*;
 
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
-/**
- * @author: syedbahm
- */
 public class RoutingTableImplTest {
 
-    private IClusterGlobalServices ics =  mock(IClusterGlobalServices.class);
-    private RoutingTableImpl rti = new RoutingTableImpl();
+  private final URI namespace = URI.create("http://cisco.com/example");
+  private final QName QNAME = new QName(namespace, "global");
 
-    private final URI namespace = URI.create("http://cisco.com/example");
-    private final QName QNAME = new QName(namespace,"global");
+  private IClusterGlobalServices clusterService;
+  private RoutingTableImpl<RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier>, String> routingTable;
+  ConcurrentMap mockGlobalRpcCache;
+  ConcurrentMap mockRpcCache;
 
-    ConcurrentMap concurrentMapMock = mock(ConcurrentMap.class);
+  @Before
+  public void setUp() throws Exception{
+    clusterService = mock(IClusterGlobalServices.class);
+    routingTable = new RoutingTableImpl<RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier>, String>();
+    mockGlobalRpcCache = new ConcurrentHashMap<>();
+    mockRpcCache = new ConcurrentHashMap<>();
+    createRoutingTableCache();
+  }
 
+  @After
+  public void tearDown(){
+    reset(clusterService);
+    mockGlobalRpcCache = null;
+    mockRpcCache = null;
+  }
 
-    @Test
-    public void testAddGlobalRoute() throws Exception {
-        ConcurrentMap concurrentMap = createRoutingTableCache();
+  @Test
+  public void addGlobalRoute_ValidArguments_ShouldAdd() throws Exception {
 
-        Assert.assertNotNull(concurrentMap);
-        RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier =  mock(RpcRouter.RouteIdentifier.class);
-        InstanceIdentifier identifier = mock(InstanceIdentifier.class);
-        when(routeIdentifier.getType()).thenReturn(QNAME);
-        when(routeIdentifier.getRoute()).thenReturn(identifier);
+    Assert.assertNotNull(mockGlobalRpcCache);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = getRouteIdentifier();
 
-        rti.addGlobalRoute(routeIdentifier, "172.27.12.1:5000");
+    final String expectedRoute = "172.27.12.1:5000";
+    routingTable.addGlobalRoute(routeIdentifier, expectedRoute);
 
-        Set<String> globalService = new HashSet<String>();
-        globalService.add("172.27.12.1:5000");
+    ConcurrentMap latestCache = routingTable.getGlobalRpcCache();
+    Assert.assertEquals(mockGlobalRpcCache, latestCache);
+    Assert.assertEquals(expectedRoute, latestCache.get(routeIdentifier));
+  }
 
-        when(concurrentMap.get(routeIdentifier)).thenReturn(globalService);
-        ConcurrentMap latestCache = rti.getRoutingTableCache();
+  @Test (expected = RoutingTable.DuplicateRouteException.class)
+  public void addGlobalRoute_DuplicateRoute_ShouldThrow() throws Exception{
 
-        Assert.assertEquals(concurrentMap,latestCache);
+    Assert.assertNotNull(mockGlobalRpcCache);
 
-        Set<String> servicesGlobal = (Set<String>)latestCache.get(routeIdentifier);
-        Assert.assertEquals(servicesGlobal.size(),1);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = getRouteIdentifier();
+    routingTable.addGlobalRoute(routeIdentifier, new String());
+    routingTable.addGlobalRoute(routeIdentifier, new String());
+  }
 
-        Assert.assertEquals(servicesGlobal.iterator().next(),"172.27.12.1:5000");
+  @Test
+  public void getGlobalRoute_ExistingRouteId_ShouldReturnRoute() throws Exception {
 
-    }
+    Assert.assertNotNull(mockGlobalRpcCache);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = getRouteIdentifier();
+    String expectedRoute = "172.27.12.1:5000";
 
-    @Test
-    public void testGetRoutes() throws Exception {
-        ConcurrentMap concurrentMap = createRoutingTableCache();
+    routingTable.addGlobalRoute(routeIdentifier, expectedRoute);
 
-        Assert.assertNotNull(concurrentMap);
-        RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier =  mock(RpcRouter.RouteIdentifier.class);
-        InstanceIdentifier identifier = mock(InstanceIdentifier.class);
-        when(routeIdentifier.getContext()).thenReturn(QNAME);
-        when(routeIdentifier.getRoute()).thenReturn(identifier);
+    String actualRoute = (String) routingTable.getGlobalRoute(routeIdentifier);
+    Assert.assertEquals(expectedRoute, actualRoute);
+  }
 
-        rti.addGlobalRoute(routeIdentifier, "172.27.12.1:5000");
+  @Test
+  public void getGlobalRoute_NonExistentRouteId_ShouldReturnNull() throws Exception {
 
-        String globalService =   "172.27.12.1:5000";
+    Assert.assertNotNull(mockGlobalRpcCache);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = getRouteIdentifier();
 
-        when(concurrentMap.get(routeIdentifier)).thenReturn(globalService);
-        ConcurrentMap latestCache = rti.getRoutingTableCache();
+    String actualRoute = (String) routingTable.getGlobalRoute(routeIdentifier);
+    Assert.assertNull(actualRoute);
+  }
 
-        Assert.assertEquals(concurrentMap,latestCache);
+  @Test
+  public void removeGlobalRoute_ExistingRouteId_ShouldRemove() throws Exception {
 
-        Set<String> servicesGlobal =  rti.getRoutes(routeIdentifier);
+    Assert.assertNotNull(mockGlobalRpcCache);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = getRouteIdentifier();
 
+    ConcurrentMap cache = routingTable.getGlobalRpcCache();
+    Assert.assertTrue(cache.size() == 0);
+    routingTable.addGlobalRoute(routeIdentifier, "172.27.12.1:5000");
+    Assert.assertTrue(cache.size() == 1);
 
-        Assert.assertEquals(servicesGlobal.size(),1);
-        Iterator<String> iterator = servicesGlobal.iterator();
-        while(iterator.hasNext()){
-        Assert.assertEquals(iterator.next(),"172.27.12.1:5000");
-        }
+    routingTable.removeGlobalRoute(routeIdentifier);
+    Assert.assertTrue(cache.size() == 0);
 
+  }
 
-    }
-    @Test
-    public void testRegisterRouteChangeListener() throws Exception {
-        Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),0);
-        rti.registerRouteChangeListener(new RouteChangeListenerImpl());
+  @Test
+  public void removeGlobalRoute_NonExistentRouteId_ShouldDoNothing() throws Exception {
 
-        Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),0); //old should not work
-        //what about the new approach - using whiteboard pattern
-        rti.setRouteChangeListener(new RouteChangeListenerImpl());
+    Assert.assertNotNull(mockGlobalRpcCache);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = getRouteIdentifier();
 
-        Assert.assertEquals(rti.getRegisteredRouteChangeListeners().size(),1); //should not work
+    ConcurrentMap cache = routingTable.getGlobalRpcCache();
+    Assert.assertTrue(cache.size() == 0);
 
+    routingTable.removeGlobalRoute(routeIdentifier);
+    Assert.assertTrue(cache.size() == 0);
 
-    }
-    @Test
-    public void testRemoveGlobalRoute()throws Exception {
+  }
 
-        ConcurrentMap concurrentMap = createRoutingTableCache();
+  @Test
+  public void addRoute_ForNewRouteId_ShouldAddRoute() throws Exception {
+    Assert.assertTrue(mockRpcCache.size() == 0);
 
-        Assert.assertNotNull(concurrentMap);
-        RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier =  mock(RpcRouter.RouteIdentifier.class);
-        InstanceIdentifier identifier = mock(InstanceIdentifier.class);
-        when(routeIdentifier.getContext()).thenReturn(QNAME);
-        when(routeIdentifier.getRoute()).thenReturn(identifier);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeId = getRouteIdentifier();
 
-        rti.addGlobalRoute(routeIdentifier, "172.27.12.1:5000");
+    routingTable.addRoute(routeId, new String());
+    Assert.assertTrue(mockRpcCache.size() == 1);
 
-        String globalService =   "172.27.12.1:5000";
+    Set<String> routes = routingTable.getRoutes(routeId);
+    Assert.assertEquals(1, routes.size());
+  }
 
-        when(concurrentMap.get(routeIdentifier)).thenReturn(globalService);
-        ConcurrentMap latestCache = rti.getRoutingTableCache();
+  @Test
+  public void addRoute_ForExistingRouteId_ShouldAppendRoute() throws Exception {
 
-        Assert.assertEquals(concurrentMap,latestCache);
+    Assert.assertTrue(mockRpcCache.size() == 0);
 
-        Set<String> servicesGlobal =  rti.getRoutes(routeIdentifier);
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeId = getRouteIdentifier();
 
+    String route_1 = "10.0.0.1:5955";
+    String route_2 = "10.0.0.2:5955";
 
-        Assert.assertEquals(servicesGlobal.size(),1);
+    routingTable.addRoute(routeId, route_1);
+    routingTable.addRoute(routeId, route_2);
 
-        Assert.assertEquals(servicesGlobal.iterator().next(),"172.27.12.1:5000");
+    Assert.assertTrue(mockRpcCache.size() == 1);
 
-        rti.removeGlobalRoute(routeIdentifier);
+    Set<String> routes = routingTable.getRoutes(routeId);
+    Assert.assertEquals(2, routes.size());
+    Assert.assertTrue(routes.contains(route_1));
+    Assert.assertTrue(routes.contains(route_2));
+  }
 
-        Assert.assertNotNull(rti.getRoutes(routeIdentifier));
+  @Test
+  public void addRoute_UsingMultipleThreads_ShouldNotOverwrite(){
+    ExecutorService threadPool = Executors.newCachedThreadPool();
 
+    int numOfRoutesToAdd = 100;
+    String routePrefix_1   = "10.0.0.1:555";
+    RpcRouter.RouteIdentifier routeId = getRouteIdentifier();
+    threadPool.submit(addRoutes(numOfRoutesToAdd, routePrefix_1, routeId));
+    String routePrefix_2   = "10.0.0.1:556";
+    threadPool.submit(addRoutes(numOfRoutesToAdd, routePrefix_2, routeId));
 
+    // wait for all tasks to complete; timeout in 10 sec
+    threadPool.shutdown();
+    try {
+      threadPool.awaitTermination(10, TimeUnit.SECONDS); //
+    } catch (InterruptedException e) {
+      e.printStackTrace();
     }
 
-    private ConcurrentMap createRoutingTableCache() throws Exception {
+    Assert.assertEquals(2*numOfRoutesToAdd, routingTable.getRoutes(routeId).size());
+  }
 
-        //here init
-        Component c = mock(Component.class);
+  @Test(expected = NullPointerException.class)
+  public void addRoute_NullRouteId_shouldThrowNpe() throws Exception {
 
-        when(ics.existCache(
-                RoutingTableImpl.ROUTING_TABLE_GLOBAL_CACHE)).thenReturn(false);
+    routingTable.addRoute(null, new String());
+  }
 
-        when(ics.createCache(RoutingTableImpl.ROUTING_TABLE_GLOBAL_CACHE, EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL))).thenReturn(concurrentMapMock);
-         rti.setClusterGlobalServices(this.ics);
-        rti.init(c);
+  @Test(expected = NullPointerException.class)
+  public void addRoute_NullRoute_shouldThrowNpe() throws Exception{
 
-        Assert.assertEquals(concurrentMapMock,rti.getRoutingTableCache() );
-        return concurrentMapMock;
+    routingTable.addRoute(getRouteIdentifier(), null);
+  }
 
-    }
+  @Test (expected = UnsupportedOperationException.class)
+  public void getRoutes_Call_ShouldReturnImmutableCopy() throws Exception{
+    Assert.assertNotNull(routingTable);
+    RpcRouter.RouteIdentifier routeId = getRouteIdentifier();
+    routingTable.addRoute(routeId, new String());
 
+    Set<String> routes = routingTable.getRoutes(routeId); //returns Immutable Set
 
-    @Test
-    public void testCreateRoutingTableCacheReturnExistingCache() throws Exception {
-        ConcurrentMap concurrentMap = createRoutingTableCache();
+    routes.add(new String()); //can not be modified; should throw
+  }
 
-        //OK here we should try creating again the cache but this time it should return the existing one
-        when(ics.existCache(
-                RoutingTableImpl.ROUTING_TABLE_GLOBAL_CACHE)).thenReturn(true);
+  @Test
+  public void getRoutes_With2RoutesFor1RouteId_ShouldReturnASetWithSize2() throws Exception{
+    Assert.assertNotNull(routingTable);
+    RpcRouter.RouteIdentifier routeId = getRouteIdentifier();
+    routingTable.addRoute(routeId, "10.0.0.1:5555");
+    routingTable.addRoute(routeId, "10.0.0.2:5555");
 
-        when(ics.getCache(
-                RoutingTableImpl.ROUTING_TABLE_GLOBAL_CACHE)).thenReturn(concurrentMap);
+    Set<String> routes = routingTable.getRoutes(routeId); //returns Immutable Set
 
+    Assert.assertEquals(2, routes.size());
+  }
 
-        //here init
-        Component c = mock(Component.class);
+  @Test
+  public void getLastAddedRoute_WhenMultipleRoutesExists_ShouldReturnLatestRoute()
+    throws Exception {
 
-        rti.init(c);
+    Assert.assertNotNull(routingTable);
+    RpcRouter.RouteIdentifier routeId = getRouteIdentifier();
+    String route_1 = "10.0.0.1:5555";
+    String route_2 = "10.0.0.2:5555";
+    routingTable.addRoute(routeId, route_1);
+    routingTable.addRoute(routeId, route_2);
 
-        Assert.assertEquals(concurrentMap,rti.getRoutingTableCache());
+    Assert.assertEquals(route_2, routingTable.getLastAddedRoute(routeId));
+  }
 
+  @Test
+  public void removeRoute_WhenMultipleRoutesExist_RemovesGivenRoute() throws Exception{
+    Assert.assertNotNull(routingTable);
+    RpcRouter.RouteIdentifier routeId = getRouteIdentifier();
+    String route_1 = "10.0.0.1:5555";
+    String route_2 = "10.0.0.2:5555";
 
+    routingTable.addRoute(routeId, route_1);
+    routingTable.addRoute(routeId, route_2);
 
+    Assert.assertEquals(2, routingTable.getRoutes(routeId).size());
 
+    routingTable.removeRoute(routeId, route_1);
+    Assert.assertEquals(1, routingTable.getRoutes(routeId).size());
 
-    }
+  }
 
-    private class RouteChangeListenerImpl<I,R> implements RouteChangeListener<I,R>{
+  @Test
+  public void removeRoute_WhenOnlyOneRouteExists_RemovesRouteId() throws Exception{
+    Assert.assertNotNull(routingTable);
+    RpcRouter.RouteIdentifier routeId = getRouteIdentifier();
+    String route_1 = "10.0.0.1:5555";
 
-        @Override
-        public void onRouteUpdated(I key, R new_value) {
-            //To change body of implemented methods use File | Settings | File Templates.
-        }
+    routingTable.addRoute(routeId, route_1);
+    Assert.assertEquals(1, routingTable.getRoutes(routeId).size());
 
-        @Override
-        public void onRouteDeleted(I key) {
-            //To change body of implemented methods use File | Settings | File Templates.
-        }
-    }
+    routingTable.removeRoute(routeId, route_1);
+    ConcurrentMap cache = routingTable.getRpcCache();
+    Assert.assertFalse(cache.containsKey(routeId));
+
+  }
 
+  /*
+   * Private helper methods
+   */
+  private void createRoutingTableCache() throws Exception {
+
+    //here init
+    Component c = mock(Component.class);
+
+    when(clusterService.existCache(
+        RoutingTableImpl.GLOBALRPC_CACHE)).thenReturn(false);
+
+    when(clusterService.createCache(RoutingTableImpl.GLOBALRPC_CACHE,
+        EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL))).
+        thenReturn(mockGlobalRpcCache);
+
+    when(clusterService.existCache(
+        RoutingTableImpl.RPC_CACHE)).thenReturn(false);
+
+    when(clusterService.createCache(RoutingTableImpl.RPC_CACHE,
+        EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL))).
+        thenReturn(mockRpcCache);
+
+    doNothing().when(clusterService).tbegin();
+    doNothing().when(clusterService).tcommit();
+
+    routingTable.setClusterGlobalServices(this.clusterService);
+    routingTable.init(c);
+
+    Assert.assertEquals(mockGlobalRpcCache, routingTable.getGlobalRpcCache());
+    Assert.assertEquals(mockRpcCache, routingTable.getRpcCache());
+  }
+
+  private RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> getRouteIdentifier(){
+    RpcRouter.RouteIdentifier<QName, QName, InstanceIdentifier> routeIdentifier = mock(RpcRouter.RouteIdentifier.class);
+    InstanceIdentifier identifier = mock(InstanceIdentifier.class);
+    when(routeIdentifier.getType()).thenReturn(QNAME);
+    when(routeIdentifier.getRoute()).thenReturn(identifier);
+
+    return routeIdentifier;
+  }
+
+  private Runnable addRoutes(final int numRoutes, final String routePrefix, final RpcRouter.RouteIdentifier routeId){
+    return new Runnable() {
+      @Override
+      public void run() {
+        for (int i=0;i<numRoutes;i++){
+          String route = routePrefix + i;
+          try {
+            routingTable.addRoute(routeId, route);
+          } catch (Exception e) {
+            e.printStackTrace();
+          }
+        }
+      }
+    };
+  }
 }