/** * 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 reading {@link ZNRecord} from {@link HelixPropertyStore}, where the {@link ZNRecord} has a map field * ({@link HelixAccountService#ACCOUNT_METADATA_MAP_KEY}: accountMap), and accountMap contains * ("accountId": badAccountJsonString). This is a NOT good {@link ZNRecord} format that should fail fetch or update. * @throws Exception Any unexpected exception. */ @Test public void testReadBadZNRecordCase5() throws Exception { Map<String, String> mapValue = new HashMap<>(); mapValue.put(String.valueOf(refAccount.getId()), BAD_ACCOUNT_METADATA_STRING); ZNRecord zNRecord = makeZNRecordWithMapField(null, ACCOUNT_METADATA_MAP_KEY, mapValue); updateAndWriteZNRecord(zNRecord, false); }
/** * Tests reading {@link ZNRecord} from {@link HelixPropertyStore}, where the {@link ZNRecord} is empty. This is a * good {@link ZNRecord} format that should NOT fail fetch or update. * @throws Exception Any unexpected exception. */ @Test public void testReadBadZNRecordCase1() throws Exception { ZNRecord zNRecord = makeZNRecordWithSimpleField(null, null, null); updateAndWriteZNRecord(zNRecord, true); }
/** * 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 {@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); }
public void testAccountUpdateConsumer() throws Exception { writeAccountsToHelixPropertyStore(idToRefAccountMap.values(), false); accountService = mockHelixAccountServiceFactory.getAccountService(); assertAccountsInAccountService(idToRefAccountMap.values(), NUM_REF_ACCOUNT, accountService); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true); assertAccountUpdateConsumers(accountsToUpdate, numOfConsumers, updatedAccountsReceivedByConsumers); accountsToUpdate.add(accountBuilder.build()); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true); assertAccountUpdateConsumers(accountsToUpdate, numOfConsumers, updatedAccountsReceivedByConsumers); Account updatedAccount = new AccountBuilder(account).name(account.getName() + "-extra").build(); accountsToUpdate = new HashSet<>(Collections.singleton(updatedAccount)); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true); assertAccountUpdateConsumers(Collections.emptySet(), 0, updatedAccountsReceivedByConsumers);
/** * Does several operations: * 1. Writes a {@link ZNRecord} to {@link HelixPropertyStore} without notifying listeners; * 2. Starts up a {@link HelixAccountService} that should fetch the {@link ZNRecord}; * 3. Updates (creates) one more {@link Account} through the {@link HelixAccountService}; * 4. Writes the same {@link ZNRecord} to {@link HelixPropertyStore} and publishes a message for {@link Account} update; * * If the {@link ZNRecord} is good, it should not affect updating operation. * @param zNRecord A {@link ZNRecord} to write to {@link HelixPropertyStore}. * @param isGoodZNRecord {code true} to indicate the {@link ZNRecord} is good, which should not affect updating * operation; {@code false} otherwise. * @throws Exception Any unexpected exception. */ private void updateAndWriteZNRecord(ZNRecord zNRecord, boolean isGoodZNRecord) throws Exception { writeZNRecordToHelixPropertyStore(zNRecord, false); accountService = mockHelixAccountServiceFactory.getAccountService(); assertEquals("Number of account is wrong", 0, accountService.getAllAccounts().size()); updateAccountsAndAssertAccountExistence(Collections.singletonList(refAccount), isGoodZNRecord ? 1 : 0, isGoodZNRecord); writeZNRecordToHelixPropertyStore(zNRecord, true); if (isGoodZNRecord) { assertAccountInAccountService(refAccount, accountService); } else { assertEquals("Number of accounts is wrong.", 0, accountService.getAllAccounts().size()); } }
/** * Pre-populates two {@link Account}s: (id=1, name="a") and (id=2, name="b") in * {@link org.apache.helix.store.HelixPropertyStore}. * @throws Exception Any unexpected exception. */ private void writeAccountsForConflictTest() throws Exception { List<Account> existingAccounts = new ArrayList<>(); existingAccounts.add(new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build()); existingAccounts.add(new AccountBuilder((short) 2, "b", AccountStatus.INACTIVE).build()); updateAccountsAndAssertAccountExistence(existingAccounts, 2, true); }
/** * Tests reading conflicting {@link Account} metadata from {@link HelixPropertyStore}. Two {@link Account}s have * different accountIds but the same accountNames. This is a BAD record that should impact fetching or updating * {@link Account}s. * @throws Exception Any unexpected exception. */ @Test public void testReadConflictAccountDataFromHelixPropertyStoreCase1() throws Exception { List<Account> conflictAccounts = new ArrayList<>(); Account account1 = new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build(); Account account2 = new AccountBuilder((short) 2, "a", AccountStatus.INACTIVE).build(); conflictAccounts.add(account1); conflictAccounts.add(account2); readAndUpdateBadRecord(conflictAccounts); }
/** * Tests updating a {@link Account}, which has a new id but a name conflicting with an existing record. The update * operation will fail. This test corresponds to case D specified in the JavaDoc of {@link AccountService}. * @throws Exception Any unexpected exception. */ @Test public void testConflictingUpdateCaseD() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); // write two accounts (1, "a") and (2, "b") writeAccountsForConflictTest(); Collection<Account> conflictAccounts = Collections.singleton((new AccountBuilder((short) 3, "a", AccountStatus.INACTIVE).build())); assertFalse("Wrong return value from update operation.", accountService.updateAccounts(conflictAccounts)); assertEquals("Wrong account number in HelixAccountService", 2, accountService.getAllAccounts().size()); assertNull("Wrong account got from HelixAccountService", accountService.getAccountById((short) 3)); }
/** * Cleans up if the store already exists. * @throws Exception Any unexpected exception. */ @After public void cleanUp() throws Exception { if (accountService != null) { accountService.close(); } assertEquals("No TopicListeners should still be attached to Notifier", 0, notifier.topicToListenersMap.getOrDefault(ACCOUNT_METADATA_CHANGE_TOPIC, Collections.emptySet()).size()); deleteStoreIfExists(); }
/** * Tests reading {@link ZNRecord} from {@link HelixPropertyStore}, where the {@link ZNRecord} has an invalid account * record and a valid account record. This is a NOT good {@link ZNRecord} format and it should fail fetch or update * operations, with none of the record should be read. * @throws Exception Any unexpected exception. */ @Test public void testReadBadZNRecordCase6() throws Exception { ZNRecord zNRecord = new ZNRecord(String.valueOf(System.currentTimeMillis())); Map<String, String> accountMap = new HashMap<>(); accountMap.put(String.valueOf(refAccount.getId()), refAccount.toJson(true).toString()); accountMap.put(String.valueOf(refAccount.getId() + 1), BAD_ACCOUNT_METADATA_STRING); zNRecord.setMapField(ACCOUNT_METADATA_MAP_KEY, accountMap); updateAndWriteZNRecord(zNRecord, false); }
/** * Updates a collection of {@link Account}s through {@link HelixAccountService}, and verifies that the * {@link Account}s have been successfully updated, so they can be queried through {@link HelixAccountService}. * @param accounts A collection of {@link Account}s to update through {@link HelixAccountService}. */ private void updateAccountsAndAssertAccountExistence(Collection<Account> accounts, int expectedAccountCount, boolean shouldUpdateSucceed) throws Exception { Collection<Account> expectedOldState = accountService.getAllAccounts(); boolean hasUpdateAccountSucceed = accountService.updateAccounts(accounts); assertEquals("Wrong update return status", shouldUpdateSucceed, hasUpdateAccountSucceed); if (shouldUpdateSucceed) { assertAccountsInAccountService(accounts, expectedAccountCount, accountService); if (helixConfigProps.containsKey(HelixAccountServiceConfig.BACKUP_DIRECTORY_KEY)) { Path oldStateBackup = Files.list(accountBackupDir) .filter(path -> path.getFileName().toString().endsWith(OLD_STATE_SUFFIX)) .max(Comparator.naturalOrder()) .get(); checkBackupFile(expectedOldState, oldStateBackup); String newStateFilename = oldStateBackup.getFileName().toString().replace(OLD_STATE_SUFFIX, NEW_STATE_SUFFIX); Path newStateBackup = oldStateBackup.getParent().resolve(newStateFilename); checkBackupFile(accountService.getAllAccounts(), newStateBackup); } else { assertEquals("No backup files should exist.", 0, Files.list(accountBackupDir).count()); } } else { assertEquals("Wrong number of accounts in accountService", expectedAccountCount, accountService.getAllAccounts().size()); } }
/** * 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); }
public void testUpdateAccount() throws Exception { writeAccountsToHelixPropertyStore(idToRefAccountMap.values(), false); accountService = mockHelixAccountServiceFactory.getAccountService(); assertAccountsInAccountService(idToRefAccountMap.values(), NUM_REF_ACCOUNT, accountService); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true); accountsToUpdate.add(accountBuilder.build()); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true); updateAccountsAndAssertAccountExistence(accountsToUpdate, 1 + NUM_REF_ACCOUNT, true);
/** * 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 reading conflicting {@link Account} metadata from {@link org.apache.helix.store.HelixPropertyStore}. * @throws Exception Any unexpected exception. */ @Test public void testReadConflictAccountDataFromHelixPropertyStoreCase2() throws Exception { List<Account> conflictAccounts = new ArrayList<>(); Account account1 = new AccountBuilder((short) 1, "a", AccountStatus.INACTIVE).build(); Account account2 = new AccountBuilder((short) 2, "a", AccountStatus.INACTIVE).build(); Account account3 = new AccountBuilder((short) 2, "b", AccountStatus.INACTIVE).build(); Account account4 = new AccountBuilder((short) 3, "b", AccountStatus.INACTIVE).build(); conflictAccounts.add(account1); conflictAccounts.add(account2); conflictAccounts.add(account3); conflictAccounts.add(account4); readAndUpdateBadRecord(conflictAccounts); }
/** * Tests updating a {@link Account}, which has the same id as an existing record, but the name conflicting with * another existing record. The update operation will fail. This test corresponds to case E specified in the JavaDoc * of {@link AccountService}. * @throws Exception Any unexpected exception. */ @Test public void testConflictingUpdateCaseE() throws Exception { accountService = mockHelixAccountServiceFactory.getAccountService(); // write two accounts (1, "a") and (2, "b") writeAccountsForConflictTest(); Collection<Account> conflictAccounts = Collections.singleton((new AccountBuilder((short) 1, "b", AccountStatus.INACTIVE).build())); assertFalse("Wrong return value from update operation.", accountService.updateAccounts(conflictAccounts)); assertEquals("Wrong account number in HelixAccountService", 2, accountService.getAllAccounts().size()); assertEquals("Wrong account name got from HelixAccountService", "a", accountService.getAccountById((short) 1).getName()); }
/** * Tests updating a {@link Account}, which has the same id as an existing record and a non-conflicting name with any * of the existing record. The new record will replace the existing record. This test corresponds to case B specified * in the JavaDoc of {@link AccountService}. * @throws Exception Any unexpected exception. */ @Test public void testNonConflictingUpdateCaseB() 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); }
/** * Tests reading {@link ZNRecord} from {@link HelixPropertyStore}, where the {@link ZNRecord} has a map field * ({@link HelixAccountService#ACCOUNT_METADATA_MAP_KEY}: accountMap), and accountMap contains * ("accountId": accountJsonStr) that does not match. This is a NOT good {@link ZNRecord} format that should * fail fetch or update. * @throws Exception Any unexpected exception. */ @Test public void testReadBadZNRecordCase4() throws Exception { Map<String, String> mapValue = new HashMap<>(); mapValue.put("-1", refAccount.toJson(true).toString()); ZNRecord zNRecord = makeZNRecordWithMapField(null, ACCOUNT_METADATA_MAP_KEY, mapValue); updateAndWriteZNRecord(zNRecord, false); }