/** * Tests the background updater for updating accounts from remote. During the initialization of * {@link HelixAccountService}, its internal {@link HelixPropertyStore} will be read to first time get account data. * Because of the background account updater, it should continuously make get calls to the {@link HelixPropertyStore}, * even no notification for account updates is received. Therefore, there will be more than 1 get calls to the * {@link HelixPropertyStore}. * @throws Exception */ @Test public void testBackgroundUpdater() throws Exception { helixConfigProps.setProperty(HelixAccountServiceConfig.UPDATER_POLLING_INTERVAL_MS_KEY, "1"); vHelixConfigProps = new VerifiableProperties(helixConfigProps); storeConfig = new HelixPropertyStoreConfig(vHelixConfigProps); String updaterThreadPrefix = UUID.randomUUID().toString(); MockHelixAccountServiceFactory mockHelixAccountServiceFactory = new MockHelixAccountServiceFactory(vHelixConfigProps, new MetricRegistry(), notifier, updaterThreadPrefix); accountService = mockHelixAccountServiceFactory.getAccountService(); CountDownLatch latch = new CountDownLatch(1); mockHelixAccountServiceFactory.getHelixStore(ZK_CONNECT_STRING, storeConfig).setReadLatch(latch); assertEquals("Wrong number of thread for account updater.", 1, numThreadsByThisName(updaterThreadPrefix)); awaitLatchOrTimeout(latch, 100); }
/** * Tests disabling the background thread. By setting the polling interval to 0ms, the accounts should not be fetched. * Therefore, after the {@link HelixAccountService} starts, there should be a single get call to the * {@link HelixPropertyStore}. * @throws Exception */ @Test public void testDisabledBackups() throws Exception { helixConfigProps.remove(HelixAccountServiceConfig.BACKUP_DIRECTORY_KEY); vHelixConfigProps = new VerifiableProperties(helixConfigProps); storeConfig = new HelixPropertyStoreConfig(vHelixConfigProps); String updaterThreadPrefix = UUID.randomUUID().toString(); MockHelixAccountServiceFactory mockHelixAccountServiceFactory = new MockHelixAccountServiceFactory(vHelixConfigProps, new MetricRegistry(), notifier, updaterThreadPrefix); accountService = mockHelixAccountServiceFactory.getAccountService(); updateAccountsAndAssertAccountExistence(Collections.singleton(refAccount), 1, true); }
/** * Tests starting up a {@link HelixAccountService}, when the corresponding {@code ZooKeeper} has account metadata * already stored on it. * @throws Exception Any unexpected exception. */ @Test public void testStartUpWithMetadataExists() throws Exception { // pre-populate account metadata in ZK. writeAccountsToHelixPropertyStore(idToRefAccountMap.values(), false); // When start, the helixAccountService should get the account metadata. accountService = mockHelixAccountServiceFactory.getAccountService(); assertAccountsInAccountService(idToRefAccountMap.values(), NUM_REF_ACCOUNT, accountService); }
@Override public AccountService getAccountService() { try { ScheduledExecutorService scheduler = accountServiceConfig.updaterPollingIntervalMs > 0 ? Utils.newScheduler(1, updaterThreadPrefix, false) : null; return new HelixAccountService(getHelixStore(accountServiceConfig.zkClientConnectString, storeConfig), accountServiceMetrics, notifier, scheduler, accountServiceConfig); } catch (Exception e) { throw new IllegalStateException("Could not instantiate HelixAccountService", e); } }
/** * Resets variables and settings, and cleans up if the store already exists. * @throws Exception Any unexpected exception. */ public HelixAccountServiceTest() throws Exception { helixConfigProps.setProperty( HelixPropertyStoreConfig.HELIX_PROPERTY_STORE_PREFIX + "zk.client.connection.timeout.ms", String.valueOf(ZK_CLIENT_CONNECTION_TIMEOUT_MS)); helixConfigProps.setProperty(HelixPropertyStoreConfig.HELIX_PROPERTY_STORE_PREFIX + "zk.client.session.timeout.ms", String.valueOf(ZK_CLIENT_SESSION_TIMEOUT_MS)); helixConfigProps.setProperty(HelixAccountServiceConfig.ZK_CLIENT_CONNECT_STRING_KEY, ZK_CONNECT_STRING); helixConfigProps.setProperty(HelixPropertyStoreConfig.HELIX_PROPERTY_STORE_PREFIX + "root.path", STORE_ROOT_PATH); accountBackupDir = Paths.get(TestUtils.getTempDir("account-backup")).toAbsolutePath(); helixConfigProps.setProperty(HelixAccountServiceConfig.BACKUP_DIRECTORY_KEY, accountBackupDir.toString()); vHelixConfigProps = new VerifiableProperties(helixConfigProps); storeConfig = new HelixPropertyStoreConfig(vHelixConfigProps); notifier = new MockNotifier<>(); mockHelixAccountServiceFactory = new MockHelixAccountServiceFactory(vHelixConfigProps, new MetricRegistry(), notifier, null); deleteStoreIfExists(); generateReferenceAccountsAndContainers(); }
/** * Tests a clean startup of {@link HelixAccountService}, when the corresponding {@code ZooKeeper} does not * have any {@link ZNRecord} on it. * @throws Exception Any unexpected exception. */ @Test public void testStartUpWithoutMetadataExists() { accountService = mockHelixAccountServiceFactory.getAccountService(); // At time zero, no account metadata exists. assertEquals("The number of account in HelixAccountService is incorrect after clean startup", 0, accountService.getAllAccounts().size()); }
/** * Tests disabling the background thread. By setting the polling interval to 0ms, the accounts should not be fetched. * Therefore, after the {@link HelixAccountService} starts, there should be a single get call to the * {@link HelixPropertyStore}. */ @Test public void testDisableBackgroundUpdater() { helixConfigProps.setProperty(HelixAccountServiceConfig.UPDATER_POLLING_INTERVAL_MS_KEY, "0"); vHelixConfigProps = new VerifiableProperties(helixConfigProps); storeConfig = new HelixPropertyStoreConfig(vHelixConfigProps); String updaterThreadPrefix = UUID.randomUUID().toString(); MockHelixAccountServiceFactory mockHelixAccountServiceFactory = new MockHelixAccountServiceFactory(vHelixConfigProps, new MetricRegistry(), notifier, updaterThreadPrefix); accountService = mockHelixAccountServiceFactory.getAccountService(); assertEquals("Wrong number of thread for account updater.", 0, numThreadsByThisName(updaterThreadPrefix)); }
/** * Delete corresponding {@code ZooKeeper} nodes of a {@link HelixPropertyStore} if exist. * @throws Exception Any unexpected exception. */ private void deleteStoreIfExists() throws Exception { HelixStoreOperator storeOperator = new HelixStoreOperator(mockHelixAccountServiceFactory.getHelixStore(ZK_CONNECT_STRING, storeConfig)); // check if the store exists by checking if root path (e.g., "/") exists in the store. if (storeOperator.exist("/")) { storeOperator.delete("/"); } }
/** * Tests creating a number of new {@link Account} through {@link HelixAccountService}, where there is no {@link ZNRecord} * exists on the {@code ZooKeeper}. */ @Test public void testCreateAccount() { accountService = mockHelixAccountServiceFactory.getAccountService(); assertEquals("The number of account in HelixAccountService is incorrect", 0, accountService.getAllAccounts().size()); boolean res = accountService.updateAccounts(idToRefAccountMap.values()); assertTrue("Failed to update accounts", res); assertAccountsInAccountService(idToRefAccountMap.values(), NUM_REF_ACCOUNT, accountService); }
public void testNullInputs() throws IOException { try { new MockHelixAccountServiceFactory(null, new MetricRegistry(), notifier, null).getAccountService(); fail("should have thrown"); } catch (NullPointerException e) { new MockHelixAccountServiceFactory(vHelixConfigProps, null, notifier, null).getAccountService(); fail("should have thrown"); } catch (NullPointerException e) { new MockHelixAccountServiceFactory(vHelixConfigProps, new MetricRegistry(), null, null).getAccountService(); accountService.close(); accountService = mockHelixAccountServiceFactory.getAccountService(); try { accountService.updateAccounts(null);
/** * Writes a {@link ZNRecord} to {@link org.apache.helix.store.HelixPropertyStore}. * @param zNRecord The {@link ZNRecord} to write. * @throws Exception Any unexpected exception. */ private void writeZNRecordToHelixPropertyStore(ZNRecord zNRecord, boolean shouldNotify) throws Exception { HelixStoreOperator storeOperator = new HelixStoreOperator(mockHelixAccountServiceFactory.getHelixStore(ZK_CONNECT_STRING, storeConfig)); storeOperator.write(HelixAccountService.FULL_ACCOUNT_METADATA_PATH, zNRecord); if (shouldNotify) { notifier.publish(ACCOUNT_METADATA_CHANGE_TOPIC, FULL_ACCOUNT_METADATA_CHANGE_MESSAGE); } }
/** * Tests receiving a bad topic, it will not be recognized by {@link HelixAccountService}, but will also not * crash the service. * @throws Exception Any unexpected exception. */ @Test public void receiveBadTopic() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); updateAccountsAndAssertAccountExistence(idToRefAccountMap.values(), NUM_REF_ACCOUNT, true); notifier.publish("badTopic", FULL_ACCOUNT_METADATA_CHANGE_MESSAGE); assertEquals("The number of account in HelixAccountService is different from expected", NUM_REF_ACCOUNT, accountService.getAllAccounts().size()); }
/** * Pre-populates a collection of {@link Account}s to the underlying {@link org.apache.helix.store.HelixPropertyStore} * using {@link com.github.ambry.clustermap.HelixStoreOperator} (not through the {@link HelixAccountService}). This method * does not check any conflict among the {@link Account}s to write. * @throws Exception Any unexpected exception. */ private void writeAccountsToHelixPropertyStore(Collection<Account> accounts, boolean shouldNotify) throws Exception { HelixStoreOperator storeOperator = new HelixStoreOperator(mockHelixAccountServiceFactory.getHelixStore(ZK_CONNECT_STRING, storeConfig)); ZNRecord zNRecord = new ZNRecord(String.valueOf(System.currentTimeMillis())); Map<String, String> accountMap = new HashMap<>(); for (Account account : accounts) { accountMap.put(String.valueOf(account.getId()), account.toJson(true).toString()); } zNRecord.setMapField(ACCOUNT_METADATA_MAP_KEY, accountMap); // Write account metadata into HelixPropertyStore. storeOperator.write(HelixAccountService.FULL_ACCOUNT_METADATA_PATH, zNRecord); if (shouldNotify) { notifier.publish(ACCOUNT_METADATA_CHANGE_TOPIC, FULL_ACCOUNT_METADATA_CHANGE_MESSAGE); } }
/** * Tests receiving a bad message, it will not be recognized by {@link HelixAccountService}, but will also not * crash the service. * @throws Exception Any unexpected exception. */ @Test public void receiveBadMessage() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); updateAccountsAndAssertAccountExistence(idToRefAccountMap.values(), NUM_REF_ACCOUNT, true); notifier.publish(ACCOUNT_METADATA_CHANGE_TOPIC, "badMessage"); assertEquals("The number of account in HelixAccountService is different from expected", NUM_REF_ACCOUNT, accountService.getAllAccounts().size()); }
/** * Tests updating a collection of {@link Account}s, where there are duplicate {@link Account}s in id and name. * @throws Exception Any unexpected exception. */ @Test public void testUpdateDuplicateAccounts() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); List<Account> conflictAccounts = new ArrayList<>(); conflictAccounts.add(new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build()); conflictAccounts.add(new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build()); assertFalse("Wrong return value from update operation.", accountService.updateAccounts(conflictAccounts)); }
/** * Tests updating a collection of {@link Account}s, where the {@link Account}s are conflicting among themselves * in name. * @throws Exception Any unexpected exception. */ @Test public void testUpdateNameConflictingAccounts() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); List<Account> conflictAccounts = new ArrayList<>(); conflictAccounts.add(new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build()); conflictAccounts.add(new AccountBuilder((short) 2, "a", AccountStatus.INACTIVE).build()); assertFalse("Wrong return value from update operation.", accountService.updateAccounts(conflictAccounts)); }
/** * PrePopulates a collection of self-conflicting {@link Account}s, which will impact {@link HelixAccountService} * startup and update. * @param accounts The self-conflicting {@link Account}s. */ private void readAndUpdateBadRecord(Collection<Account> accounts) throws Exception { writeAccountsToHelixPropertyStore(accounts, false); accountService = mockHelixAccountServiceFactory.getAccountService(); assertEquals("Wrong number of accounts in helixAccountService", 0, accountService.getAllAccounts().size()); updateAccountsAndAssertAccountExistence(Collections.singletonList(refAccount), 0, false); writeAccountsToHelixPropertyStore(accounts, true); assertEquals("Number of account is wrong.", 0, accountService.getAllAccounts().size()); }
/** * Tests updating a collection of {@link Account}s, where the {@link Account}s are conflicting among themselves * in id. * @throws Exception Any unexpected exception. */ @Test public void testUpdateIdConflictingAccounts() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); List<Account> conflictAccounts = new ArrayList<>(); conflictAccounts.add(new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build()); conflictAccounts.add(new AccountBuilder((short) 1, "b", AccountStatus.INACTIVE).build()); assertFalse("Wrong return value from update operation.", accountService.updateAccounts(conflictAccounts)); }
/** * Tests updating a {@link Account}, which has a new id and name different from any of the existing record. The * new record will replace the existing record. This test corresponds to case C specified in the JavaDoc of * {@link AccountService}. * @throws Exception Any unexpected exception. */ @Test public void testNonConflictingUpdateCaseC() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); // write two accounts (1, "a") and (2, "b") writeAccountsForConflictTest(); Collection<Account> nonConflictAccounts = Collections.singleton((new AccountBuilder((short) 3, "c", AccountStatus.ACTIVE).build())); updateAccountsAndAssertAccountExistence(nonConflictAccounts, 3, true); }
/** * Tests updating a {@link Account}, which has the same id and name as an existing record, and will replace the * existing record. This test corresponds to case A specified in the JavaDoc of {@link AccountService}. * @throws Exception Any unexpected exception. */ @Test public void testNonConflictingUpdateCaseA() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); // write two accounts (1, "a") and (2, "b") writeAccountsForConflictTest(); Account accountToUpdate = accountService.getAccountById((short) 1); Collection<Account> nonConflictAccounts = Collections.singleton(new AccountBuilder(accountToUpdate).status(AccountStatus.ACTIVE).build()); updateAccountsAndAssertAccountExistence(nonConflictAccounts, 2, true); }