/* * Copyright 2012-2013 LinkedIn, Inc * * 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 voldemort.store.system; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.concurrent.Callable; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import voldemort.ClusterTestUtils; import voldemort.ServerTestUtils; import voldemort.TestUtils; import voldemort.client.ClientConfig; import voldemort.client.SystemStoreClient; import voldemort.client.SystemStoreClientFactory; import voldemort.client.SystemStoreRepository; import voldemort.client.scheduler.AsyncMetadataVersionManager; import voldemort.cluster.Cluster; import voldemort.common.service.SchedulerService; import voldemort.server.VoldemortConfig; import voldemort.server.VoldemortServer; import voldemort.store.StoreDefinition; import voldemort.store.socket.SocketStoreFactory; import voldemort.store.socket.clientrequest.ClientRequestExecutorPool; import voldemort.utils.MetadataVersionStoreUtils; import voldemort.utils.SystemTime; /** * Test class to verify the AsyncMetadataVersionManager * * @author csoman * */ @RunWith(Parameterized.class) public class AsyncMetadataVersionManagerTest { private static String storesXmlfile = "test/common/voldemort/config/stores.xml"; String[] bootStrapUrls = null; private SocketStoreFactory socketStoreFactory = new ClientRequestExecutorPool(2, 10000, 100000, 32 * 1024); private VoldemortServer[] servers; private Cluster cluster; public static String socketUrl = ""; protected int clientZoneId; private long newVersion = 0; private SystemStoreClient<String, String> sysVersionStore; private SystemStoreRepository repository; private SchedulerService scheduler; private AsyncMetadataVersionManager asyncCheckMetadata; private boolean callbackDone = false; private long updatedClusterVersion; private long updatedStoreVersion; private List<StoreDefinition> storeDefs; // Multi store version tracking test List<AsyncMetadataVersionManager> asyncVersionManagers = new ArrayList<AsyncMetadataVersionManager>(); long[] updatedVersions = new long[3]; boolean[] callbacksDone = new boolean[3]; public AsyncMetadataVersionManagerTest(Cluster cluster, List<StoreDefinition> storeDefs, int clientZoneId) { this.cluster = cluster; this.storeDefs = storeDefs; this.clientZoneId = clientZoneId; } @Parameterized.Parameters public static Collection<Object[]> configs() { return Arrays.asList(new Object[][] { { ClusterTestUtils.getZ1Z3Z5ClusterWithNonContiguousNodeIds(), ClusterTestUtils.getZ1Z3Z5StoreDefsBDB(), 3 }, { ClusterTestUtils.getZZZCluster(), ClusterTestUtils.getZZZ322StoreDefs("bdb"), 0 } }); } @Before public void setUp() throws Exception { servers = new VoldemortServer[cluster.getNodeIds().size()]; int i = 0; for(Integer nodeId: cluster.getNodeIds()) { VoldemortConfig config = ServerTestUtils.createServerConfigWithDefs(true, nodeId, TestUtils.createTempDir() .getAbsolutePath(), cluster, storeDefs, new Properties()); VoldemortServer server = ServerTestUtils.startVoldemortServer(socketStoreFactory, config); servers[i++] = server; } socketUrl = servers[0].getIdentityNode().getSocketUrl().toString(); bootStrapUrls = new String[1]; bootStrapUrls[0] = socketUrl; ClientConfig clientConfig = new ClientConfig(); clientConfig.setBootstrapUrls(bootStrapUrls).setClientZoneId(clientZoneId); SystemStoreClientFactory<String, String> systemStoreFactory = new SystemStoreClientFactory<String, String>(clientConfig); sysVersionStore = systemStoreFactory.createSystemStore(SystemStoreConstants.SystemStoreName.voldsys$_metadata_version_persistence.name()); repository = new SystemStoreRepository(clientConfig); repository.addSystemStore(sysVersionStore, SystemStoreConstants.SystemStoreName.voldsys$_metadata_version_persistence.name()); this.scheduler = new SchedulerService(2, SystemTime.INSTANCE, true); } @After public void tearDown() throws Exception { for(VoldemortServer server: servers) { ServerTestUtils.stopVoldemortServer(server); } } /* * Validates that the AsyncMetadataVersionManager correctly identifies the * version update. This is done by initializing the base metadata version * (for cluster.xml), starting the AsyncMetadataVersionManager and then * updating the version to a new value. For the test to succeed the callback * has to be invoked correctly by the asynchronous manager. */ @Test(timeout = 60000) public void testBasicAsyncBehaviour() { String storeVersionKey = "cluster.xml"; try { Callable<Void> rebootstrapCallback = new Callable<Void>() { public Void call() throws Exception { callbackForClusterChange(); return null; } }; // Write a base version of 100 Properties versionProps = MetadataVersionStoreUtils.getProperties(this.sysVersionStore); versionProps.setProperty(storeVersionKey, Long.toString(100)); MetadataVersionStoreUtils.setProperties(this.sysVersionStore, versionProps); // Giving enough time to complete the above put. Thread.sleep(500); // Starting the Version Metadata Manager this.asyncCheckMetadata = new AsyncMetadataVersionManager(this.repository, rebootstrapCallback, null); scheduler.schedule(asyncCheckMetadata.getClass().getName(), asyncCheckMetadata, new Date(), 500); // Wait until the Version Manager is active while(!asyncCheckMetadata.isActive) { Thread.sleep(500); } // Updating the version metadata here for the Version Metadata // Manager to detect this.newVersion = 101; System.err.println("Incrementing the version for : " + storeVersionKey); versionProps.setProperty(storeVersionKey, Long.toString(this.newVersion)); MetadataVersionStoreUtils.setProperties(this.sysVersionStore, versionProps); while(!callbackDone) { Thread.sleep(2000); } assertEquals(this.updatedClusterVersion, this.newVersion); } catch(Exception e) { e.printStackTrace(); fail("Failed to start the Metadata Version Manager : " + e.getMessage()); } } /* * Validates that the AsyncMetadataVersionManager correctly identifies the * store specific version update. This is done by initializing the base * metadata version (for a particular store), starting the * AsyncMetadataVersionManager and then updating the version to a new value. * For the test to succeed the callback has to be invoked correctly by the * asynchronous manager. */ @Test(timeout = 60000) public void testStoreDefinitionChangeTracker() { String storeVersionKey = "users"; Callable<Void> rebootstrapCallback = new Callable<Void>() { public Void call() throws Exception { callbackForStoreChange(); return null; } }; try { // Write a base version of 100 Properties versionProps = MetadataVersionStoreUtils.getProperties(this.sysVersionStore); versionProps.setProperty(storeVersionKey, Long.toString(100)); MetadataVersionStoreUtils.setProperties(this.sysVersionStore, versionProps); // Giving enough time to complete the above put. Thread.sleep(500); // Starting the Version Metadata Manager this.asyncCheckMetadata = new AsyncMetadataVersionManager(this.repository, rebootstrapCallback, storeVersionKey); scheduler.schedule(asyncCheckMetadata.getClass().getName(), asyncCheckMetadata, new Date(), 500); // Wait until the Version Manager is active while(!asyncCheckMetadata.isActive) { Thread.sleep(500); } // Updating the version metadata here for the Version Metadata // Manager to detect this.newVersion = 101; System.err.println("Incrementing the version for : " + storeVersionKey); versionProps.setProperty(storeVersionKey, Long.toString(this.newVersion)); MetadataVersionStoreUtils.setProperties(this.sysVersionStore, versionProps); while(!callbackDone) { Thread.sleep(2000); } assertEquals(false, (this.updatedStoreVersion == 0)); assertEquals(this.updatedStoreVersion, this.newVersion); } catch(Exception e) { e.printStackTrace(); fail("Failed to start the Metadata Version Manager : " + e.getMessage()); } } /* * Validates that the AsyncMetadataVersionManager correctly identifies the * store specific version update in the presence of multiple stores. This is * done by initializing the base metadata version (for a particular store), * starting the AsyncMetadataVersionManager and then updating the version to * a new value. For the test to succeed the callback has to be invoked * correctly by the asynchronous manager. * * This test also checks that callback is invoked only for the corresponding * store that was updated. Other callbacks should not be invoked. */ @Test(timeout = 60000) public void testMultipleStoreDefinitionsChangeTracker() { String storeVersionKeys[] = new String[3]; for(int i = 0; i < 3; i++) { storeVersionKeys[i] = "users" + i; } List<StoresVersionCallback> rebootstrapCallbacks = new ArrayList<AsyncMetadataVersionManagerTest.StoresVersionCallback>(); for(int i = 0; i < 3; i++) { StoresVersionCallback callback = new StoresVersionCallback(i); rebootstrapCallbacks.add(callback); } try { // Write a base version of 100 Properties versionProps = MetadataVersionStoreUtils.getProperties(this.sysVersionStore); for(int i = 0; i < 3; i++) { versionProps.setProperty(storeVersionKeys[i], Long.toString(100)); } MetadataVersionStoreUtils.setProperties(this.sysVersionStore, versionProps); // Giving enough time to complete the above put. Thread.sleep(500); // Starting the Version Metadata Managers for(int i = 0; i < 3; i++) { AsyncMetadataVersionManager versionManager = new AsyncMetadataVersionManager(this.repository, rebootstrapCallbacks.get(i), storeVersionKeys[i]); asyncVersionManagers.add(versionManager); scheduler.schedule(versionManager.getClass().getName(), versionManager, new Date(), 500); } // Wait until the Version Manager is active while(!asyncVersionManagers.get(0).isActive && !asyncVersionManagers.get(1).isActive && !asyncVersionManagers.get(2).isActive) { Thread.sleep(500); } // Updating the version metadata here for the Version Metadata // Manager to detect this.newVersion = 101; // Set this new version on the store: 'users1' System.out.println("Incrementing the version for : " + storeVersionKeys[1]); versionProps.setProperty(storeVersionKeys[1], Long.toString(this.newVersion)); MetadataVersionStoreUtils.setProperties(this.sysVersionStore, versionProps); while(!callbacksDone[0] && !callbacksDone[1] && !callbacksDone[2]) { Thread.sleep(2000); } // Check that version for index 1 has been udpated correctly. assertEquals(false, (this.updatedVersions[1] == 0)); long updatedVersion = this.updatedVersions[1]; assertEquals(updatedVersion, this.newVersion); // Check that versions for other indices have not changed assertEquals(true, (this.updatedVersions[0] == 0)); assertEquals(true, (this.updatedVersions[2] == 0)); } catch(Exception e) { e.printStackTrace(); fail("Failed to start the Metadata Version Manager : " + e.getMessage()); } } private void callbackForClusterChange() { try { Long clusterVersion = this.asyncCheckMetadata.getClusterMetadataVersion(); if(clusterVersion != null) { this.updatedClusterVersion = clusterVersion; } } catch(Exception e) { e.printStackTrace(); fail("Error in updating cluster.xml version: " + e.getMessage()); } finally { this.callbackDone = true; } } private void callbackForStoreChange() { try { Long storeVersion = this.asyncCheckMetadata.getStoreMetadataVersion(); if(storeVersion != null) { this.updatedStoreVersion = storeVersion; } } catch(Exception e) { e.printStackTrace(); fail("Error in updating store version: " + e.getMessage()); } finally { this.callbackDone = true; } } private void callbacksForStoreChange(int index) { try { Long storeVersion = this.asyncVersionManagers.get(index).getStoreMetadataVersion(); if(storeVersion != null) { this.updatedVersions[index] = storeVersion; } } catch(Exception e) { e.printStackTrace(); fail("Error in updating store version: " + e.getMessage()); } finally { this.callbacksDone[index] = true; } } class StoresVersionCallback implements Callable<Void> { private int index; public StoresVersionCallback(int index) { this.index = index; } @Override public Void call() throws Exception { callbacksForStoreChange(this.index); return null; } } }