blob: e6e4e72fe407bea86c963fea90b320a7254320c7 [file] [log] [blame]
package net.onrc.onos.core.registry;
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.core.registry.StandaloneRegistryTest.LoggingCallback;
import net.onrc.onos.core.registry.ZookeeperRegistry.SwitchLeaderListener;
import net.onrc.onos.core.util.IdBlock;
import net.onrc.onos.core.util.OnosInstanceId;
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.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.projectfloodlight.openflow.util.HexString;
import static org.easymock.EasyMock.anyBoolean;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
/**
* 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.
*/
@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 static final Long ID_BLOCK_SIZE = 0x100000000L;
protected ZookeeperRegistry registry;
protected CuratorFramework client;
protected PathChildrenCacheListener pathChildrenCacheListener;
protected static 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.
*/
@Override
@Before
public void setUp() throws Exception {
super.setUp();
pathChildrenCacheListener = null;
// Mock of CuratorFramework
client = createCuratorFrameworkMock();
// Mock of CuratorFrameworkFactory
PowerMock.mockStatic(CuratorFrameworkFactory.class);
expect(CuratorFrameworkFactory.newClient((String) anyObject(),
anyInt(), anyInt(), (RetryPolicy) 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).
*/
@Override
@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#getOnosInstanceId()} correctly returns
* registered ID.
*
* @throws Exception
*/
@Test
public void testGetOnosInstanceId() throws Exception {
String controllerIdToRegister = "controller1";
// try before controller is registered
OnosInstanceId onosInstanceId = registry.getOnosInstanceId();
assertNull(onosInstanceId);
// register
registry.registerController(controllerIdToRegister);
// call getOnosInstanceId and verify
onosInstanceId = registry.getOnosInstanceId();
assertNotNull(onosInstanceId);
assertEquals(controllerIdToRegister, onosInstanceId.toString());
}
/**
* 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, IControllerRegistryService.ControlChangeCallback)}
* correctly take control of specific switch.
* Because {@link ZookeeperRegistry#requestControl(long, 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 = createMock(LeaderLatch.class);
latch.addListener(anyObject(SwitchLeaderListener.class));
expectLastCall().once();
latch.start();
expectLastCall().once();
replay(latch);
PowerMock.expectNew(LeaderLatch.class,
anyObject(CuratorFramework.class),
anyObject(String.class),
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());
}
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 = createMock(LeaderLatch.class);
latch.addListener(anyObject(SwitchLeaderListener.class));
expectLastCall().once();
latch.start();
expectLastCall().once();
latch.removeListener(anyObject(SwitchLeaderListener.class));
expectLastCall().once();
latch.close();
expectLastCall().once();
replay(latch);
PowerMock.expectNew(LeaderLatch.class,
anyObject(CuratorFramework.class),
anyObject(String.class),
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);
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 = createMock(LeaderLatch.class);
latch.addListener(anyObject(SwitchLeaderListener.class));
expectLastCall().once();
latch.start();
expectLastCall().once();
expect(latch.hasLeadership()).andReturn(true).anyTimes();
latch.removeListener(anyObject(SwitchLeaderListener.class));
expectLastCall().once();
latch.close();
expectLastCall().once();
replay(latch);
PowerMock.expectNew(LeaderLatch.class,
anyObject(CuratorFramework.class),
anyObject(String.class),
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));
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 numBlocks = 100;
ArrayList<IdBlock> blocks = new ArrayList<IdBlock>(numBlocks);
for (int i = 0; i < numBlocks; ++i) {
IdBlock block = registry.allocateUniqueIdBlock();
assertNotNull(block);
blocks.add(block);
}
for (int i = 0; i < numBlocks; ++i) {
IdBlock block1 = blocks.get(i);
for (int j = i + 1; j < numBlocks; ++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 = createMock(AtomicValue.class);
expect(atomicValue.succeeded()).andReturn(true).anyTimes();
expect(atomicValue.preValue()).andAnswer(new IAnswer<Long>() {
private long value = 0;
@Override
public Long answer() throws Throwable {
value += ID_BLOCK_SIZE;
return value;
}
}).anyTimes();
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();
replay(atomicValue);
// Mock DistributedAtomicLong
DistributedAtomicLong daLong = createMock(DistributedAtomicLong.class);
expect(daLong.add(anyLong())).andReturn(atomicValue).anyTimes();
replay(daLong);
PowerMock.expectNew(DistributedAtomicLong.class,
new Class<?>[]{CuratorFramework.class, String.class, RetryPolicy.class},
anyObject(CuratorFramework.class),
anyObject(String.class),
anyObject(RetryPolicy.class)).
andReturn(daLong).anyTimes();
PowerMock.replay(DistributedAtomicLong.class);
// Mock ListenerContainer
ListenerContainer<PathChildrenCacheListener> listenerContainer = createMock(ListenerContainer.class);
listenerContainer.addListener(anyObject(PathChildrenCacheListener.class));
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
pathChildrenCacheListener = (PathChildrenCacheListener) getCurrentArguments()[0];
return null;
}
}).once();
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,
anyObject(CuratorFramework.class), anyObject(String.class), anyBoolean()).
andReturn(pathChildrenCacheMain).once();
PowerMock.expectNew(PathChildrenCache.class,
anyObject(CuratorFramework.class), anyObject(String.class), anyBoolean()).
andReturn(pathChildrenCache1).once();
PowerMock.expectNew(PathChildrenCache.class,
anyObject(CuratorFramework.class), anyObject(String.class), anyBoolean()).
andReturn(pathChildrenCache2).anyTimes();
PowerMock.replay(PathChildrenCache.class);
// Mock ServiceCache
ServiceCache<ControllerService> serviceCache = createMock(ServiceCache.class);
serviceCache.start();
expectLastCall().once();
expect(serviceCache.getInstances()).andReturn(new ArrayList<ServiceInstance<ControllerService>>() { {
add(createServiceInstanceMock("controller1"));
add(createServiceInstanceMock("controller2"));
} }).anyTimes();
replay(serviceCache);
// Mock ServiceCacheBuilder
ServiceCacheBuilder<ControllerService> serviceCacheBuilder = createMock(ServiceCacheBuilder.class);
expect(serviceCacheBuilder.name(anyObject(String.class)))
.andReturn(serviceCacheBuilder).once();
expect(serviceCacheBuilder.build()).andReturn(serviceCache).once();
replay(serviceCacheBuilder);
// Mock ServiceDiscovery
ServiceDiscovery<ControllerService> serviceDiscovery = createMock(ServiceDiscovery.class);
serviceDiscovery.start();
expectLastCall().once();
expect(serviceDiscovery.serviceCacheBuilder()).andReturn(serviceCacheBuilder).once();
serviceDiscovery.registerService(anyObject(ServiceInstance.class));
expectLastCall().once();
replay(serviceDiscovery);
// Mock CuratorFramework
CuratorFramework mockClient = createMock(CuratorFramework.class);
mockClient.start();
expectLastCall().once();
expect(mockClient.usingNamespace(anyObject(String.class))).andReturn(mockClient);
replay(mockClient);
// Mock ServiceDiscoveryBuilder
ServiceDiscoveryBuilder<ControllerService> builder = createMock(ServiceDiscoveryBuilder.class);
expect(builder.client(mockClient)).andReturn(builder).once();
expect(builder.basePath(anyObject(String.class))).andReturn(builder);
expect(builder.build()).andReturn(serviceDiscovery);
replay(builder);
PowerMock.mockStatic(ServiceDiscoveryBuilder.class);
expect(ServiceDiscoveryBuilder.builder(ControllerService.class)).andReturn(builder).once();
PowerMock.replay(ServiceDiscoveryBuilder.class);
return mockClient;
}
/**
* 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 = createMock(ControllerService.class);
expect(controllerService.getControllerId()).andReturn(controllerId).anyTimes();
replay(controllerService);
@SuppressWarnings("unchecked")
ServiceInstance<ControllerService> serviceInstance = createMock(ServiceInstance.class);
expect(serviceInstance.getPayload()).andReturn(controllerService).anyTimes();
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 = createMock(PathChildrenCache.class);
expect(pathChildrenCache.getListenable()).andReturn(listener).anyTimes();
pathChildrenCache.start(anyObject(StartMode.class));
expectLastCall().anyTimes();
List<ChildData> childs = new ArrayList<ChildData>();
for (String path : paths) {
childs.add(createChildDataMockForCurrentData(controllerId, path));
}
expect(pathChildrenCache.getCurrentData()).andReturn(childs).anyTimes();
pathChildrenCache.rebuild();
expectLastCall().anyTimes();
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 = createMock(ChildData.class);
expect(data.getPath()).andReturn(path + "-0").anyTimes();
expect(data.getData()).andReturn(controllerId.getBytes()).anyTimes();
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 = createMock(PathChildrenCacheEvent.class);
ChildData data = createMock(ChildData.class);
expect(data.getPath()).andReturn(path).anyTimes();
expect(data.getData()).andReturn(controllerId.getBytes()).anyTimes();
replay(data);
expect(event.getType()).andReturn(type).anyTimes();
expect(event.getData()).andReturn(data).anyTimes();
replay(event);
return event;
}
}