/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.hive.hcatalog.templeton; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeoutException; import java.util.concurrent.Future; import org.apache.hive.hcatalog.templeton.tool.TempletonControllerJob; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.mockito.Mockito; import org.mockito.stubbing.Answer; /* * Base class for mocking job operations with concurrent requests. */ public class ConcurrentJobRequestsTestBase { private static final Logger LOG = LoggerFactory.getLogger(ConcurrentJobRequestsTestBase.class); private boolean started = false; private Object lock = new Object(); MockAnswerTestHelper<QueueStatusBean> statusJobHelper = new MockAnswerTestHelper<QueueStatusBean>(); MockAnswerTestHelper<QueueStatusBean> killJobHelper = new MockAnswerTestHelper<QueueStatusBean>(); MockAnswerTestHelper<List<JobItemBean>> listJobHelper = new MockAnswerTestHelper<List<JobItemBean>>(); MockAnswerTestHelper<Integer> submitJobHelper = new MockAnswerTestHelper<Integer>(); /* * Waits for other threads to join and returns with its Id. */ private int waitForAllThreadsToStart(JobRunnable jobRunnable, int poolThreadCount) { int currentId = jobRunnable.threadStartCount.incrementAndGet(); LOG.info("Waiting for other threads with thread id: " + currentId); synchronized(lock) { /* * We need a total of poolThreadCount + 1 threads to start at same. There are * poolThreadCount threads in thread pool and another one which has started them. * The thread which sees atomic counter as poolThreadCount+1 is the last thread` * to join and wake up all threads to start all at once. */ if (currentId > poolThreadCount) { LOG.info("Waking up all threads: " + currentId); started = true; this.lock.notifyAll(); } else { while (!started) { try { this.lock.wait(); } catch (InterruptedException ignore) { } } } } return currentId; } public JobRunnable ConcurrentJobsStatus(final int threadCount, AppConfig appConfig, final boolean killThreads, boolean interruptThreads, final Answer<QueueStatusBean> answer) throws IOException, InterruptedException, QueueException, NotAuthorizedException, BadParam, BusyException { StatusDelegator delegator = new StatusDelegator(appConfig); final StatusDelegator mockDelegator = Mockito.spy(delegator); Mockito.doAnswer(answer).when(mockDelegator).getJobStatus(Mockito.any(String.class), Mockito.any(String.class)); JobRunnable statusJobRunnable = new JobRunnable() { @Override public void run() { try { int threadId = waitForAllThreadsToStart(this, threadCount); LOG.info("Started executing Job Status operation. ThreadId : " + threadId); mockDelegator.run("admin", "job_1000" + threadId); } catch (Exception ex) { exception = ex; } } }; executeJobOperations(statusJobRunnable, threadCount, killThreads, interruptThreads); return statusJobRunnable; } public JobRunnable ConcurrentListJobs(final int threadCount, AppConfig config, final boolean killThreads, boolean interruptThreads, final Answer<List<JobItemBean>> answer) throws IOException, InterruptedException, QueueException, NotAuthorizedException, BadParam, BusyException { ListDelegator delegator = new ListDelegator(config); final ListDelegator mockDelegator = Mockito.spy(delegator); Mockito.doAnswer(answer).when(mockDelegator).listJobs(Mockito.any(String.class), Mockito.any(boolean.class), Mockito.any(String.class), Mockito.any(int.class), Mockito.any(boolean.class)); JobRunnable listJobRunnable = new JobRunnable() { @Override public void run() { try { int threadId = waitForAllThreadsToStart(this, threadCount); LOG.info("Started executing Job List operation. ThreadId : " + threadId); mockDelegator.run("admin", true, "", 10, true); } catch (Exception ex) { exception = ex; } } }; executeJobOperations(listJobRunnable, threadCount, killThreads, interruptThreads); return listJobRunnable; } public JobRunnable SubmitConcurrentJobs(final int threadCount, AppConfig config, final boolean killThreads, boolean interruptThreads, final Answer<Integer> responseAnswer, final Answer<QueueStatusBean> timeoutResponseAnswer, final String jobIdResponse) throws IOException, InterruptedException, QueueException, NotAuthorizedException, BusyException, TimeoutException, Exception { LauncherDelegator delegator = new LauncherDelegator(config); final LauncherDelegator mockDelegator = Mockito.spy(delegator); final List<String> listArgs = new ArrayList<String>(); TempletonControllerJob mockCtrl = Mockito.mock(TempletonControllerJob.class); Mockito.doReturn(jobIdResponse).when(mockCtrl).getSubmittedId(); Mockito.doReturn(mockCtrl).when(mockDelegator).getTempletonController(); Mockito.doAnswer(responseAnswer).when(mockDelegator).runTempletonControllerJob( Mockito.any(TempletonControllerJob.class), Mockito.any(List.class)); Mockito.doAnswer(timeoutResponseAnswer).when(mockDelegator).killJob( Mockito.any(String.class), Mockito.any(String.class)); Mockito.doNothing().when(mockDelegator).registerJob(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class), Mockito.any(Map.class)); JobRunnable submitJobRunnable = new JobRunnable() { @Override public void run() { try { int threadId = waitForAllThreadsToStart(this, threadCount); LOG.info("Started executing Job Submit operation. ThreadId : " + threadId); mockDelegator.enqueueController("admin", null, "", listArgs); } catch (Throwable ex) { exception = ex; } } }; executeJobOperations(submitJobRunnable, threadCount, killThreads, interruptThreads); return submitJobRunnable; } public void executeJobOperations(JobRunnable jobRunnable, int threadCount, boolean killThreads, boolean interruptThreads) throws IOException, InterruptedException, QueueException, NotAuthorizedException { started = false; ExecutorService executorService = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());; ArrayList<Future<?>> futures = new ArrayList<Future<?>>(); for (int i = 0; i < threadCount; i++) { futures.add(executorService.submit(jobRunnable)); } waitForAllThreadsToStart(jobRunnable, threadCount); LOG.info("Started all threads "); if (killThreads) { executorService.shutdownNow(); } else { if (interruptThreads){ for (Future<?> future : futures) { LOG.info("Cancelling the thread"); future.cancel(true); } } executorService.shutdown(); } /* * For both graceful or forceful shutdown, wait for tasks to terminate such that * appropriate exceptions are raised and stored in JobRunnable.exception. */ if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { LOG.info("Force Shutting down the pool\n"); if (!killThreads) { /* * killThreads option has already done force shutdown. No need to do again. */ executorService.shutdownNow(); } } } public abstract class JobRunnable implements Runnable { public volatile Throwable exception = null; public AtomicInteger threadStartCount = new AtomicInteger(0); } }