blob: 774a9d47096c7af8ea6f440a947a52bf1f72e671 [file] [log] [blame]
package net.onrc.onos.registry.controller;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.test.FloodlightTestCase;
import net.onrc.onos.registry.controller.StandaloneRegistryTest.LoggingCallback;
import net.onrc.onos.registry.controller.ZookeeperRegistry.SwitchLeaderListener;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.listen.ListenerContainer;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.x.discovery.ServiceCache;
import org.apache.curator.x.discovery.ServiceCacheBuilder;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openflow.util.HexString;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
/**
* Unit test for {@link ZookeeperRegistry}.
* NOTE: {@link FloodlightTestCase} conflicts with PowerMock. If FloodLight-related methods need to be tested,
* implement another test class to test them.
* @author Naoki Shiota
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ZookeeperRegistry.class, CuratorFramework.class, CuratorFrameworkFactory.class,
ServiceDiscoveryBuilder.class, ServiceDiscovery.class, ServiceCache.class, PathChildrenCache.class,
ZookeeperRegistry.SwitchPathCacheListener.class})
public class ZookeeperRegistryTest extends FloodlightTestCase {
private final static Long ID_BLOCK_SIZE = 0x100000000L;
protected ZookeeperRegistry registry;
protected CuratorFramework client;
protected PathChildrenCacheListener pathChildrenCacheListener;
protected final String CONTROLLER_ID = "controller2013";
/**
* Initialize {@link ZookeeperRegistry} Object and inject initial value with {@link ZookeeperRegistry#init(FloodlightModuleContext)} method.
* This setup code also tests {@link ZookeeperRegistry#init(FloodlightModuleContext)} method itself.
*/
@Before
public void setUp() throws Exception {
super.setUp();
pathChildrenCacheListener = null;
// Mock of CuratorFramework
client = createCuratorFrameworkMock();
// Mock of CuratorFrameworkFactory
PowerMock.mockStatic(CuratorFrameworkFactory.class);
EasyMock.expect(CuratorFrameworkFactory.newClient((String)EasyMock.anyObject(),
EasyMock.anyInt(), EasyMock.anyInt(), (RetryPolicy)EasyMock.anyObject())).andReturn(client);
PowerMock.replay(CuratorFrameworkFactory.class);
FloodlightModuleContext fmc = new FloodlightModuleContext();
registry = new ZookeeperRegistry();
fmc.addService(ZookeeperRegistry.class, registry);
registry.init(fmc);
PowerMock.verify(client, CuratorFrameworkFactory.class);
}
/**
* Clean up member variables (empty for now).
*/
@After
public void tearDown() throws Exception {
super.tearDown();
}
/**
* Test if {@link ZookeeperRegistry#registerController(String)} method can go through without exception.
* (Exceptions are usually out of test target, but {@link ZookeeperRegistry#registerController(String)} throws an exception in case of invalid registration.)
*/
@Test
public void testRegisterController() {
String controllerIdToRegister = "controller2013";
try {
registry.registerController(controllerIdToRegister);
} catch (RegistryException e) {
e.printStackTrace();
fail(e.getMessage());
}
}
/**
* Test if {@link ZookeeperRegistry#getControllerId()} correctly returns registered ID.
* @throws Exception
*/
@Test
public void testGetControllerId() throws Exception {
String controllerIdToRegister = "controller1";
// try before controller is registered
String controllerId = registry.getControllerId();
assertNull(controllerId);
// register
registry.registerController(controllerIdToRegister);
// call getControllerId and verify
controllerId = registry.getControllerId();
assertNotNull(controllerId);
assertEquals(controllerIdToRegister, controllerId);
}
/**
* Test if {@link ZookeeperRegistry#getAllControllers()} returns all controllers.
* Controllers to be returned are injected while setup. See {@link ZookeeperRegistryTest#createCuratorFrameworkMock()}
* to what controllers are injected using mock {@link ServiceCache}.
* @throws Exception
*/
@Test
public void testGetAllControllers() throws Exception {
String controllerIdRegistered = "controller1";
String controllerIdNotRegistered = "controller2013";
try {
Collection<String> ctrls = registry.getAllControllers();
assertTrue(ctrls.contains(controllerIdRegistered));
assertFalse(ctrls.contains(controllerIdNotRegistered));
} catch (RegistryException e) {
e.printStackTrace();
fail(e.getMessage());
}
}
/**
* Test if {@link ZookeeperRegistry#requestControl(long, net.onrc.onos.registry.controller.IControllerRegistryService.ControlChangeCallback)}
* correctly take control of specific switch. Because {@link ZookeeperRegistry#requestControl(long, net.onrc.onos.registry.controller.IControllerRegistryService.ControlChangeCallback)}
* doesn't return values, inject mock {@link LeaderLatch} object and verify latch is correctly set up.
* @throws Exception
*/
@Test
public void testRequestControl() throws Exception {
// Mock LeaderLatch
LeaderLatch latch = EasyMock.createMock(LeaderLatch.class);
latch.addListener(EasyMock.anyObject(SwitchLeaderListener.class));
EasyMock.expectLastCall().once();
latch.start();
EasyMock.expectLastCall().once();
EasyMock.replay(latch);
PowerMock.expectNew(LeaderLatch.class,
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyObject(String.class))
.andReturn(latch).once();
PowerMock.replay(LeaderLatch.class);
String controllerId = "controller2013";
registry.registerController(controllerId);
LoggingCallback callback = new LoggingCallback(1);
long dpidToRequest = 2000L;
try {
registry.requestControl(dpidToRequest, callback);
} catch (RegistryException e) {
e.printStackTrace();
fail(e.getMessage());
}
EasyMock.verify(latch);
}
/**
* Test if {@link ZookeeperRegistry#releaseControl(long)} correctly release control of specific switch.
* Because {@link ZookeeperRegistry#releaseControl(long)} doesn't return values, inject mock
* {@link LeaderLatch} object and verify latch is correctly set up.
* @throws Exception
*/
@Test
public void testReleaseControl() throws Exception {
// Mock of LeaderLatch
LeaderLatch latch = EasyMock.createMock(LeaderLatch.class);
latch.addListener(EasyMock.anyObject(SwitchLeaderListener.class));
EasyMock.expectLastCall().once();
latch.start();
EasyMock.expectLastCall().once();
latch.removeListener(EasyMock.anyObject(SwitchLeaderListener.class));
EasyMock.expectLastCall().once();
latch.close();
EasyMock.expectLastCall().once();
EasyMock.replay(latch);
PowerMock.expectNew(LeaderLatch.class,
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyObject(String.class))
.andReturn(latch).once();
PowerMock.replay(LeaderLatch.class);
String controllerId = "controller2013";
registry.registerController(controllerId);
long dpidToRequest = 2000L;
LoggingCallback callback = new LoggingCallback(1);
registry.requestControl(dpidToRequest, callback);
registry.releaseControl(dpidToRequest);
EasyMock.verify(latch);
}
/**
* Test if {@link ZookeeperRegistry#hasControl(long)} returns correct status whether controller has control of specific switch.
* @throws Exception
*/
@Test
public void testHasControl() throws Exception {
// Mock of LeaderLatch
LeaderLatch latch = EasyMock.createMock(LeaderLatch.class);
latch.addListener(EasyMock.anyObject(SwitchLeaderListener.class));
EasyMock.expectLastCall().once();
latch.start();
EasyMock.expectLastCall().once();
EasyMock.expect(latch.hasLeadership()).andReturn(true).anyTimes();
latch.removeListener(EasyMock.anyObject(SwitchLeaderListener.class));
EasyMock.expectLastCall().once();
latch.close();
EasyMock.expectLastCall().once();
EasyMock.replay(latch);
PowerMock.expectNew(LeaderLatch.class,
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyObject(String.class))
.andReturn(latch);
PowerMock.replay(LeaderLatch.class);
String controllerId = "controller2013";
registry.registerController(controllerId);
long dpidToRequest = 2000L;
LoggingCallback callback = new LoggingCallback(2);
// Test before request control
assertFalse(registry.hasControl(dpidToRequest));
registry.requestControl(dpidToRequest, callback);
// Test after request control
assertTrue(registry.hasControl(dpidToRequest));
registry.releaseControl(dpidToRequest);
// Test after release control
assertFalse(registry.hasControl(dpidToRequest));
EasyMock.verify(latch);
}
/**
* Test if {@link ZookeeperRegistry#getControllerForSwitch(long)} correctly returns controller ID of specific switch.
* Relation between controllers and switches are defined by {@link ZookeeperRegistryTest#setPathChildrenCache()} function.
* @throws Throwable
*/
@Test
public void testGetControllerForSwitch() throws Throwable {
long dpidRegistered = 1000L;
long dpidNotRegistered = 2000L;
setPathChildrenCache();
String controllerForSw = registry.getControllerForSwitch(dpidRegistered);
assertEquals("controller1",controllerForSw);
controllerForSw = registry.getControllerForSwitch(dpidNotRegistered);
assertEquals(null, controllerForSw);
}
/**
* Test if {@link ZookeeperRegistry#getSwitchesControlledByController(String)} returns correct list of
* switches controlled by a controller.
* @throws Exception
*/
// TODO: Test after getSwitchesControlledByController() is implemented.
@Ignore @Test
public void testGetSwitchesControlledByController() throws Exception {
String controllerIdRegistered = "controller1";
String dpidRegistered = HexString.toHexString(1000L);
String controllerIdNotRegistered = CONTROLLER_ID;
Collection<Long> switches = registry.getSwitchesControlledByController(controllerIdRegistered);
assertNotNull(switches);
assertTrue(switches.contains(dpidRegistered));
switches = registry.getSwitchesControlledByController(controllerIdNotRegistered);
assertNotNull(switches);
assertEquals(0, switches.size());
}
/**
* Test if {@link ZookeeperRegistry#getAllSwitches()} returns correct list of all switches.
* Switches are injected in {@link ZookeeperRegistryTest#setPathChildrenCache()} function.
* @throws Exception
*/
@Test
public void testGetAllSwitches() throws Exception {
String [] dpids = {
HexString.toHexString(1000L),
HexString.toHexString(1001L),
HexString.toHexString(1002L),
};
setPathChildrenCache();
Map<String, List<ControllerRegistryEntry>> switches = registry.getAllSwitches();
assertNotNull(switches);
assertEquals(dpids.length, switches.size());
for(String dpid : dpids) {
assertTrue(switches.keySet().contains(dpid));
}
}
/**
* Test if {@link ZookeeperRegistry#allocateUniqueIdBlock()} can assign IdBlock without duplication.
*/
@Test
public void testAllocateUniqueIdBlock() {
// Number of blocks to be verified that any of them has unique block
final int NUM_BLOCKS = 100;
ArrayList<IdBlock> blocks = new ArrayList<IdBlock>(NUM_BLOCKS);
for(int i = 0; i < NUM_BLOCKS; ++i) {
IdBlock block = registry.allocateUniqueIdBlock();
assertNotNull(block);
blocks.add(block);
}
for(int i = 0; i < NUM_BLOCKS; ++i) {
IdBlock block1 = blocks.get(i);
for(int j = i + 1; j < NUM_BLOCKS; ++j) {
IdBlock block2 = blocks.get(j);
IdBlock lower,higher;
if(block1.getStart() < block2.getStart()) {
lower = block1;
higher = block2;
} else {
lower = block2;
higher = block1;
}
assertTrue(lower.getSize() > 0L);
assertTrue(higher.getSize() > 0L);
assertTrue(lower.getEnd() <= higher.getStart());
}
}
}
//-------------------------- Creation of mock objects --------------------------
/**
* Create mock {@link CuratorFramework} object with initial value below.<br>
* [Ctrl ID] : [DPID]<br>
* controller1 : 1000<br>
* controller2 : 1001<br>
* controller2 : 1002<br>
* controller2013 : nothing
* @return Created mock object
* @throws Exception
*/
@SuppressWarnings({ "serial", "unchecked" })
private CuratorFramework createCuratorFrameworkMock() throws Exception {
// Mock of AtomicValue
AtomicValue<Long> atomicValue = EasyMock.createMock(AtomicValue.class);
EasyMock.expect(atomicValue.succeeded()).andReturn(true).anyTimes();
EasyMock.expect(atomicValue.preValue()).andAnswer(new IAnswer<Long>() {
private long value = 0;
@Override
public Long answer() throws Throwable {
value += ID_BLOCK_SIZE;
return value;
}
}).anyTimes();
EasyMock.expect(atomicValue.postValue()).andAnswer(new IAnswer<Long>() {
private long value = ID_BLOCK_SIZE;
@Override
public Long answer() throws Throwable {
value += ID_BLOCK_SIZE;
return value;
}
}).anyTimes();
EasyMock.replay(atomicValue);
// Mock DistributedAtomicLong
DistributedAtomicLong daLong = EasyMock.createMock(DistributedAtomicLong.class);
EasyMock.expect(daLong.add(EasyMock.anyLong())).andReturn(atomicValue).anyTimes();
EasyMock.replay(daLong);
PowerMock.expectNew(DistributedAtomicLong.class,
new Class<?> [] {CuratorFramework.class, String.class, RetryPolicy.class},
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyObject(RetryPolicy.class)).
andReturn(daLong).anyTimes();
PowerMock.replay(DistributedAtomicLong.class);
// Mock ListenerContainer
ListenerContainer<PathChildrenCacheListener> listenerContainer = EasyMock.createMock(ListenerContainer.class);
listenerContainer.addListener(EasyMock.anyObject(PathChildrenCacheListener.class));
EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
pathChildrenCacheListener = (PathChildrenCacheListener)EasyMock.getCurrentArguments()[0];
return null;
}
}).once();
EasyMock.replay(listenerContainer);
// Mock PathChildrenCache
PathChildrenCache pathChildrenCacheMain = createPathChildrenCacheMock(CONTROLLER_ID, new String[] {"/switches"}, listenerContainer);
PathChildrenCache pathChildrenCache1 = createPathChildrenCacheMock("controller1", new String[] {HexString.toHexString(1000L)}, listenerContainer);
PathChildrenCache pathChildrenCache2 = createPathChildrenCacheMock("controller2", new String[] {
HexString.toHexString(1001L), HexString.toHexString(1002L) },listenerContainer);
// Mock PathChildrenCache constructor
PowerMock.expectNew(PathChildrenCache.class,
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyBoolean()).
andReturn(pathChildrenCacheMain).once();
PowerMock.expectNew(PathChildrenCache.class,
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyBoolean()).
andReturn(pathChildrenCache1).once();
PowerMock.expectNew(PathChildrenCache.class,
EasyMock.anyObject(CuratorFramework.class), EasyMock.anyObject(String.class), EasyMock.anyBoolean()).
andReturn(pathChildrenCache2).anyTimes();
PowerMock.replay(PathChildrenCache.class);
// Mock ServiceCache
ServiceCache<ControllerService> serviceCache = EasyMock.createMock(ServiceCache.class);
serviceCache.start();
EasyMock.expectLastCall().once();
EasyMock.expect(serviceCache.getInstances()).andReturn(new ArrayList<ServiceInstance<ControllerService> > () {{
add(createServiceInstanceMock("controller1"));
add(createServiceInstanceMock("controller2"));
}}).anyTimes();
EasyMock.replay(serviceCache);
// Mock ServiceCacheBuilder
ServiceCacheBuilder<ControllerService> serviceCacheBuilder = EasyMock.createMock(ServiceCacheBuilder.class);
EasyMock.expect(serviceCacheBuilder.name(EasyMock.anyObject(String.class))).andReturn(serviceCacheBuilder).once();
EasyMock.expect(serviceCacheBuilder.build()).andReturn(serviceCache).once();
EasyMock.replay(serviceCacheBuilder);
// Mock ServiceDiscovery
ServiceDiscovery<ControllerService> serviceDiscovery = EasyMock.createMock(ServiceDiscovery.class);
serviceDiscovery.start();
EasyMock.expectLastCall().once();
EasyMock.expect(serviceDiscovery.serviceCacheBuilder()).andReturn(serviceCacheBuilder).once();
serviceDiscovery.registerService(EasyMock.anyObject(ServiceInstance.class));
EasyMock.expectLastCall().once();
EasyMock.replay(serviceDiscovery);
// Mock CuratorFramework
CuratorFramework client = EasyMock.createMock(CuratorFramework.class);
client.start();
EasyMock.expectLastCall().once();
EasyMock.expect(client.usingNamespace(EasyMock.anyObject(String.class))).andReturn(client);
EasyMock.replay(client);
// Mock ServiceDiscoveryBuilder
ServiceDiscoveryBuilder<ControllerService> builder = EasyMock.createMock(ServiceDiscoveryBuilder.class);
EasyMock.expect(builder.client(client)).andReturn(builder).once();
EasyMock.expect(builder.basePath(EasyMock.anyObject(String.class))).andReturn(builder);
EasyMock.expect(builder.build()).andReturn(serviceDiscovery);
EasyMock.replay(builder);
PowerMock.mockStatic(ServiceDiscoveryBuilder.class);
EasyMock.expect(ServiceDiscoveryBuilder.builder(ControllerService.class)).andReturn(builder).once();
PowerMock.replay(ServiceDiscoveryBuilder.class);
return client;
}
/**
* Create mock {@link ServiceInstance} object using given controller ID.
* @param controllerId Controller ID to represent instance's payload (ControllerService).
* @return Mock ServiceInstance object
*/
private ServiceInstance<ControllerService> createServiceInstanceMock(String controllerId) {
ControllerService controllerService = EasyMock.createMock(ControllerService.class);
EasyMock.expect(controllerService.getControllerId()).andReturn(controllerId).anyTimes();
EasyMock.replay(controllerService);
@SuppressWarnings("unchecked")
ServiceInstance<ControllerService> serviceInstance = EasyMock.createMock(ServiceInstance.class);
EasyMock.expect(serviceInstance.getPayload()).andReturn(controllerService).anyTimes();
EasyMock.replay(serviceInstance);
return serviceInstance;
}
/**
* Create mock {@link PathChildrenCache} using given controller ID and DPIDs.
* @param controllerId Controller ID to represent current data.
* @param paths List of HexString indicating switch's DPID.
* @param listener Callback object to be set as Listenable.
* @return Mock PathChildrenCache object
* @throws Exception
*/
private PathChildrenCache createPathChildrenCacheMock(final String controllerId, final String [] paths,
ListenerContainer<PathChildrenCacheListener> listener) throws Exception {
PathChildrenCache pathChildrenCache = EasyMock.createMock(PathChildrenCache.class);
EasyMock.expect(pathChildrenCache.getListenable()).andReturn(listener).anyTimes();
pathChildrenCache.start(EasyMock.anyObject(StartMode.class));
EasyMock.expectLastCall().anyTimes();
List<ChildData> childs = new ArrayList<ChildData>();
for(String path : paths) {
childs.add(createChildDataMockForCurrentData(controllerId,path));
}
EasyMock.expect(pathChildrenCache.getCurrentData()).andReturn(childs).anyTimes();
pathChildrenCache.rebuild();
EasyMock.expectLastCall().anyTimes();
EasyMock.replay(pathChildrenCache);
return pathChildrenCache;
}
/**
* Create mock {@link ChildData} for {@link PathChildrenCache#getCurrentData()} return value.
* This object need to include 'sequence number' in tail of path string. ("-0" means 0th sequence)
* @param controllerId Controller ID
* @param path HexString indicating switch's DPID
* @return Mock ChildData object
*/
private ChildData createChildDataMockForCurrentData(String controllerId, String path) {
ChildData data = EasyMock.createMock(ChildData.class);
EasyMock.expect(data.getPath()).andReturn(path + "-0").anyTimes();
EasyMock.expect(data.getData()).andReturn(controllerId.getBytes()).anyTimes();
EasyMock.replay(data);
return data;
}
/**
* Inject relations between controllers and switches using callback object.
* @throws Exception
*/
private void setPathChildrenCache() throws Exception {
pathChildrenCacheListener.childEvent(client,
createChildrenCacheEventMock("controller1", HexString.toHexString(1000L), PathChildrenCacheEvent.Type.CHILD_ADDED));
pathChildrenCacheListener.childEvent(client,
createChildrenCacheEventMock("controller2", HexString.toHexString(1001L), PathChildrenCacheEvent.Type.CHILD_ADDED));
pathChildrenCacheListener.childEvent(client,
createChildrenCacheEventMock("controller2", HexString.toHexString(1002L), PathChildrenCacheEvent.Type.CHILD_ADDED));
}
/**
* Create mock {@link PathChildrenCacheEvent} object using given controller ID and DPID.
* @param controllerId Controller ID.
* @param path HexString of DPID.
* @param type Event type to be set to mock object.
* @return Mock PathChildrenCacheEvent object
*/
private PathChildrenCacheEvent createChildrenCacheEventMock(String controllerId, String path,
PathChildrenCacheEvent.Type type) {
PathChildrenCacheEvent event = EasyMock.createMock(PathChildrenCacheEvent.class);
ChildData data = EasyMock.createMock(ChildData.class);
EasyMock.expect(data.getPath()).andReturn(path).anyTimes();
EasyMock.expect(data.getData()).andReturn(controllerId.getBytes()).anyTimes();
EasyMock.replay(data);
EasyMock.expect(event.getType()).andReturn(type).anyTimes();
EasyMock.expect(event.getData()).andReturn(data).anyTimes();
EasyMock.replay(event);
return event;
}
}