/** * Save the zookeeper state from before the update to disk. * The following file name format will be used: {@code {yyyyMMdd}T{HHmmss}.{unique long}.old}. * If there are multiple files with the same timestamp, the unique long will prevent the file names from clashing. */ private void maybePersistOldState(Map<String, String> oldState) { if (backupPrefixAndPath != null) { try { writeBackup(backupPrefixAndPath.getSecond(), oldState); } catch (Exception e) { logger.error("Could not write previous state backup file", e); accountServiceMetrics.backupErrorCount.inc(); } } }
/** * If a backup file has not yet been created, reserve a new one with the following file name format: * {@code {yyyyMMdd}T{HHmmss}.{unique long}.old}. * @return a {@link Pair} containing the unique filename prefix for this account update and the path to use for * previous state backups. * @throws IOException */ private Pair<String, Path> reserveBackupFile() throws IOException { String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMATTER); for (long n = 0; n < Long.MAX_VALUE; n++) { String prefix = timestamp + SEP + n + SEP; Path filepath = backupDirPath.resolve(prefix + OLD_STATE_SUFFIX); try { return new Pair<>(prefix, Files.createFile(filepath)); } catch (FileAlreadyExistsException e) { // retry with a new suffix. } } throw new IOException("Could not create a unique file with timestamp " + timestamp); }
/** * Save the zookeeper state from after the update to disk. This will only save the file if the update succeeded * and the old state backup file was successfully reserved. * The following file name format will be used: {@code {yyyyMMdd}T{HHmmss}.{unique long}.new}. * @param succeeded {@code true} iff the update succeeded. */ void maybePersistNewState(boolean succeeded) { if (backupPrefixAndPath != null && succeeded) { try { Path filepath = backupDirPath.resolve(backupPrefixAndPath.getFirst() + NEW_STATE_SUFFIX); writeBackup(filepath, potentialNewState); } catch (Exception e) { logger.error("Could not write new state backup file", e); accountServiceMetrics.backupErrorCount.inc(); } } }
refAccountId = Utils.getRandomShort(random); accountIdSet.add(refAccountId); refAccountName = UUID.randomUUID().toString(); refAccountStatus = random.nextBoolean() ? AccountStatus.ACTIVE : AccountStatus.INACTIVE; refContainerId = Utils.getRandomShort(random); refContainerName = UUID.randomUUID().toString(); refContainerStatus = random.nextBoolean() ? ContainerStatus.ACTIVE : ContainerStatus.INACTIVE; refContainerPreviousEncryption = refContainerEncryption || random.nextBoolean(); refContainerMediaScanDisabled = random.nextBoolean(); refReplicationPolicy = UtilsTest.getRandomString(10); refContainerTtlRequired = random.nextBoolean(); refContainer = new ContainerBuilder(refContainerId, refContainerName, refContainerStatus, refContainerDescription,
@Override public AccountService getAccountService() { try { long startTimeMs = System.currentTimeMillis(); logger.info("Starting a HelixAccountService"); HelixPropertyStore<ZNRecord> helixStore = CommonUtils.createHelixPropertyStore(accountServiceConfig.zkClientConnectString, storeConfig, null); logger.info("HelixPropertyStore started with zkClientConnectString={}, zkClientSessionTimeoutMs={}, " + "zkClientConnectionTimeoutMs={}, rootPath={}", accountServiceConfig.zkClientConnectString, storeConfig.zkClientSessionTimeoutMs, storeConfig.zkClientConnectionTimeoutMs, storeConfig.rootPath); ScheduledExecutorService scheduler = accountServiceConfig.updaterPollingIntervalMs > 0 ? Utils.newScheduler(1, HELIX_ACCOUNT_UPDATER_PREFIX, false) : null; HelixAccountService helixAccountService = new HelixAccountService(helixStore, accountServiceMetrics, notifier, scheduler, accountServiceConfig); long spentTimeMs = System.currentTimeMillis() - startTimeMs; logger.info("HelixAccountService started, took {} ms", spentTimeMs); accountServiceMetrics.startupTimeInMs.update(spentTimeMs); return helixAccountService; } catch (Exception e) { throw new IllegalStateException("Could not instantiate HelixAccountService", e); } } }
/** * 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); }
/** * Asserts the {@link Account}s received by the {@link Consumer} are as expected. * @param expectedAccounts The expected collection of {@link Account}s that should be received by the {@link Consumer}s. * @param expectedNumberOfConsumers The expected number of {@link Consumer}s. * @param accountsInConsumers A list of collection of {@link Account}s, where each collection of {@link Account}s are * received by one {@link Consumer}. */ private void assertAccountUpdateConsumers(Set<Account> expectedAccounts, int expectedNumberOfConsumers, List<Collection<Account>> accountsInConsumers) throws Exception { assertEquals("Wrong number of consumers", expectedNumberOfConsumers, accountsInConsumers.size()); for (Collection<Account> accounts : accountsInConsumers) { assertEquals("Wrong number of updated accounts received by consumers", expectedAccounts.size(), accounts.size()); for (Account account : accounts) { assertTrue("Account update not received by consumers", expectedAccounts.contains(account)); } TestUtils.assertException(UnsupportedOperationException.class, () -> accounts.add(InMemoryUnknownAccountService.UNKNOWN_ACCOUNT), null); } }
@Override public void close() { if (open.compareAndSet(true, false)) { if (notifier != null) { notifier.unsubscribe(ACCOUNT_METADATA_CHANGE_TOPIC, changeTopicListener); } if (scheduler != null) { shutDownExecutorService(scheduler, config.updaterShutDownTimeoutMs, TimeUnit.MILLISECONDS); } helixStore.stop(); } }
/** * 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)); }
/** * 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(); }
idToRefContainerMap.clear(); for (int i = 0; i < accountCount; i++) { short accountId = Utils.getRandomShort(random); if (!accountIdSet.add(accountId)) { i--; Set<Short> containerIdSet = new HashSet<>(); for (int j = 0; j < containerCountPerAccount; j++) { short containerId = Utils.getRandomShort(random); if (!containerIdSet.add(containerId)) { j--; boolean containerPreviousEncryption = containerEncryption || random.nextBoolean(); boolean mediaScanDisabled = random.nextBoolean(); String replicationPolicy = UtilsTest.getRandomString(10); boolean ttlRequired = random.nextBoolean(); Container container = new ContainerBuilder(containerId, containerName, containerStatus, containerDescription,
@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); } }
@Test public void testAllMethods() throws Exception { assertEquals("Wrong account", null, accountService.getAccountById(Utils.getRandomShort(random))); assertEquals("Wrong account", InMemoryUnknownAccountService.UNKNOWN_ACCOUNT, accountService.getAccountById((short) -1)); assertEquals("Wrong account", InMemoryUnknownAccountService.UNKNOWN_ACCOUNT, accountService.getAccountByName(UtilsTest.getRandomString(10))); assertEquals("Wrong size of account collection", 1, accountService.getAllAccounts().size()); // updating the InMemoryUnknownAccountService should fail. Account account = new AccountBuilder((short) 1, "a", Account.AccountStatus.INACTIVE).build(); assertFalse("Wrong return value from an unsuccessful update operation", accountService.updateAccounts(Collections.singletonList(account))); assertEquals("Wrong size of account collection", 1, accountService.getAllAccounts().size()); try { accountService.getAllAccounts().add(account); fail("Should have thrown."); } catch (UnsupportedOperationException e) { // expected } accountService.close(); }