/** * Copyright (c) 2019. Qubole 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. See accompanying LICENSE file. */ package com.qubole.rubix.bookkeeper; import com.codahale.metrics.MetricRegistry; import com.qubole.rubix.common.metrics.BookKeeperMetrics; import com.qubole.rubix.common.utils.TestUtil; import com.qubole.rubix.spi.BookKeeperFactory; import com.qubole.rubix.spi.CacheConfig; import com.qubole.rubix.spi.RetryingPooledBookkeeperClient; import com.qubole.rubix.spi.fop.Poolable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.thrift.shaded.transport.TSocket; import org.apache.thrift.shaded.transport.TTransport; import org.apache.thrift.shaded.transport.TTransportException; import org.mockito.ArgumentMatchers; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; public class TestHeartbeatService { private static final Log log = LogFactory.getLog(TestHeartbeatService.class); private static final String TEST_CACHE_DIR_PREFIX = TestUtil.getTestCacheDirPrefix("TestHeartbeatService"); private static final int TEST_MAX_DISKS = 1; private static final int TEST_MAX_RETRIES = 5; private static final int TEST_RETRY_INTERVAL = 500; private static final String TEST_REMOTE_LOCATION = "testLocation"; private final Configuration conf = new Configuration(); @BeforeClass public void setUpForClass() throws IOException { CacheConfig.setCacheDataDirPrefix(conf, TEST_CACHE_DIR_PREFIX); TestUtil.createCacheParentDirectories(conf, TEST_MAX_DISKS); } @BeforeMethod public void setUp() throws InterruptedException { CacheConfig.setCacheDataDirPrefix(conf, TEST_CACHE_DIR_PREFIX); CacheConfig.setMaxDisks(conf, TEST_MAX_DISKS); CacheConfig.setServiceRetryInterval(conf, TEST_RETRY_INTERVAL); CacheConfig.setHeartbeatInterval(conf, TEST_RETRY_INTERVAL); CacheConfig.setServiceMaxRetries(conf, TEST_MAX_RETRIES); CacheConfig.setOnMaster(conf, true); BaseServerTest.startCoordinatorBookKeeperServer(conf, new MetricRegistry()); } @AfterMethod public void tearDown() { conf.clear(); BaseServerTest.stopBookKeeperServer(); } @AfterClass public void tearDownForClass() throws IOException { CacheConfig.setCacheDataDirPrefix(conf, TEST_CACHE_DIR_PREFIX); CacheConfig.setMaxDisks(conf, TEST_MAX_DISKS); TestUtil.removeCacheParentDirectories(conf, TEST_MAX_DISKS); } /** * Verify that the heartbeat service correctly makes a connection using a BookKeeper client. * * @throws TTransportException if the BookKeeper client cannot be created. */ @Test public void testHeartbeatRetryLogic_noRetriesNeeded() throws TTransportException, IOException { final BookKeeperFactory bookKeeperFactory = mock(BookKeeperFactory.class); when(bookKeeperFactory.createBookKeeperClient(anyString(), ArgumentMatchers.<Configuration>any())).thenReturn( new RetryingPooledBookkeeperClient( new Poolable<TTransport>(new TSocket("localhost", CacheConfig.getBookKeeperServerPort(conf), CacheConfig.getServerConnectTimeout(conf)), null, "localhost"), "localhost", conf)); // Disable default reporters for this BookKeeper, since they will conflict with the running server. CacheConfig.setMetricsReporters(conf, ""); try (BookKeeperMetrics bookKeeperMetrics = new BookKeeperMetrics(conf, new MetricRegistry())) { final BookKeeper bookKeeper = new CoordinatorBookKeeper(conf, bookKeeperMetrics); final HeartbeatService heartbeatService = new HeartbeatService(conf, new MetricRegistry(), bookKeeperFactory, bookKeeper); } } /** * Verify that the heartbeat service correctly makes a connection using a BookKeeper client after a number of retries. */ @Test public void testHeartbeatRetryLogic_connectAfterRetries() throws IOException { BaseServerTest.stopBookKeeperServer(); BaseServerTest.startCoordinatorBookKeeperServerWithDelay(conf, new MetricRegistry(), TEST_RETRY_INTERVAL * 2); // Disable default reporters for this BookKeeper, since they will conflict with the running server. CacheConfig.setMetricsReporters(conf, ""); try (BookKeeperMetrics bookKeeperMetrics = new BookKeeperMetrics(conf, new MetricRegistry())) { final BookKeeper bookKeeper = new CoordinatorBookKeeper(conf, bookKeeperMetrics); final HeartbeatService heartbeatService = new HeartbeatService(conf, new MetricRegistry(), new BookKeeperFactory(), bookKeeper); } } /** * Verify that the heartbeat service no longer attempts to connect once it runs out of retry attempts. * * @throws TTransportException if the BookKeeper client cannot be created. */ @Test(expectedExceptions = RuntimeException.class) public void testHeartbeatRetryLogic_outOfRetries() throws TTransportException, IOException { final BookKeeperFactory bookKeeperFactory = mock(BookKeeperFactory.class); when(bookKeeperFactory.createBookKeeperClient(anyString(), ArgumentMatchers.<Configuration>any())).thenThrow(TTransportException.class); // Disable default reporters for this BookKeeper, since they will conflict with the running server. CacheConfig.setMetricsReporters(conf, ""); try (BookKeeperMetrics bookKeeperMetrics = new BookKeeperMetrics(conf, new MetricRegistry())) { final BookKeeper bookKeeper = new CoordinatorBookKeeper(conf, bookKeeperMetrics); final HeartbeatService heartbeatService = new HeartbeatService(conf, new MetricRegistry(), bookKeeperFactory, bookKeeper); } } /** * Verify that the validation success metrics are correctly registered when validation is enabled. * * @throws TTransportException if the BookKeeper client cannot be created. */ @Test public void verifyValidationMetricsAreCorrectlyRegistered_validationEnabled() throws IOException, TTransportException { CacheConfig.setValidationEnabled(conf, true); final BookKeeperFactory bookKeeperFactory = mock(BookKeeperFactory.class); when(bookKeeperFactory.createBookKeeperClient(anyString(), ArgumentMatchers.<Configuration>any())).thenReturn( new RetryingPooledBookkeeperClient( new Poolable<TTransport>(new TSocket("localhost", CacheConfig.getBookKeeperServerPort(conf), CacheConfig.getServerConnectTimeout(conf)), null, "localhost"), "localhost", conf)); final MetricRegistry metrics = new MetricRegistry(); // Disable default reporters for this BookKeeper, since they will conflict with the running server. CacheConfig.setMetricsReporters(conf, ""); try (BookKeeperMetrics bookKeeperMetrics = new BookKeeperMetrics(conf, metrics)) { final BookKeeper bookKeeper = new CoordinatorBookKeeper(conf, bookKeeperMetrics); final HeartbeatService heartbeatService = new HeartbeatService(conf, metrics, bookKeeperFactory, bookKeeper); assertNotNull(metrics.getGauges().get(BookKeeperMetrics.ValidationMetric.CACHING_VALIDATION_SUCCESS_GAUGE.getMetricName()), "Caching validation success metric should be registered!"); assertNotNull(metrics.getGauges().get(BookKeeperMetrics.ValidationMetric.FILE_VALIDATION_SUCCESS_GAUGE.getMetricName()), "File validation success metric should be registered!"); } } /** * Verify that the validation success metrics are not registered when validation is disabled. * * @throws TTransportException if the BookKeeper client cannot be created. */ @Test public void verifyValidationMetricsAreCorrectlyRegistered_validationDisabled() throws IOException, TTransportException { CacheConfig.setValidationEnabled(conf, false); final BookKeeperFactory bookKeeperFactory = mock(BookKeeperFactory.class); when(bookKeeperFactory.createBookKeeperClient(anyString(), ArgumentMatchers.<Configuration>any())).thenReturn( new RetryingPooledBookkeeperClient( new Poolable<TTransport>(new TSocket("localhost", CacheConfig.getBookKeeperServerPort(conf), CacheConfig.getServerConnectTimeout(conf)), null, "localhost"), "localhost", conf)); final MetricRegistry metrics = new MetricRegistry(); // Disable default reporters for this BookKeeper, since they will conflict with the running server. CacheConfig.setMetricsReporters(conf, ""); try (BookKeeperMetrics bookKeeperMetrics = new BookKeeperMetrics(conf, metrics)) { final BookKeeper bookKeeper = new CoordinatorBookKeeper(conf, bookKeeperMetrics); final HeartbeatService heartbeatService = new HeartbeatService(conf, metrics, bookKeeperFactory, bookKeeper); assertNull(metrics.getGauges().get(BookKeeperMetrics.ValidationMetric.CACHING_VALIDATION_SUCCESS_GAUGE.getMetricName()), "Caching validation success metric should not be registered!"); assertNull(metrics.getGauges().get(BookKeeperMetrics.ValidationMetric.FILE_VALIDATION_SUCCESS_GAUGE.getMetricName()), "File validation success metric should not be registered!"); } } }