Java tutorial
/* * * Copyright 2016 Netflix, 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. * */ package com.netflix.genie.web.tasks.job; import com.netflix.genie.common.dto.JobExecution; import com.netflix.genie.common.dto.JobStatusMessages; import com.netflix.genie.common.exceptions.GenieTimeoutException; import com.netflix.genie.web.events.GenieEventBus; import com.netflix.genie.web.events.JobFinishedEvent; import com.netflix.genie.web.events.KillJobEvent; import com.netflix.genie.web.properties.JobsProperties; import com.netflix.genie.web.tasks.GenieTaskScheduleType; import com.netflix.genie.web.util.ProcessChecker; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.Executor; import org.apache.commons.lang3.SystemUtils; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.context.ApplicationEvent; import java.io.File; import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; /** * Unit tests for JobMonitor. * * @author tgianos * @since 3.0.0 */ public class JobMonitorTest { private static final long DELAY = 180235L; private static final long MAX_STD_OUT_LENGTH = 108234203L; private static final long MAX_STD_ERR_LENGTH = 18023482L; private JobMonitor monitor; private JobExecution jobExecution; private Executor executor; private GenieEventBus genieEventBus; private MeterRegistry registry; private File stdOut; private File stdErr; private Counter successfulCheckRate; private Counter timeoutRate; private Counter finishedRate; private Counter unsuccessfulCheckRate; private Counter stdOutTooLarge; private Counter stdErrTooLarge; private ProcessChecker processChecker; /** * Setup for the tests. */ @Before public void setup() { final Instant tomorrow = Instant.now().plus(1, ChronoUnit.DAYS); this.jobExecution = new JobExecution.Builder(UUID.randomUUID().toString()).withProcessId(3808) .withCheckDelay(DELAY).withTimeout(tomorrow).withId(UUID.randomUUID().toString()).build(); this.executor = Mockito.mock(Executor.class); this.genieEventBus = Mockito.mock(GenieEventBus.class); this.successfulCheckRate = Mockito.mock(Counter.class); this.timeoutRate = Mockito.mock(Counter.class); this.finishedRate = Mockito.mock(Counter.class); this.unsuccessfulCheckRate = Mockito.mock(Counter.class); this.stdOutTooLarge = Mockito.mock(Counter.class); this.stdErrTooLarge = Mockito.mock(Counter.class); this.registry = Mockito.mock(MeterRegistry.class); this.stdOut = Mockito.mock(File.class); this.stdErr = Mockito.mock(File.class); this.processChecker = Mockito.mock(ProcessChecker.class); Mockito.when(this.registry.counter("genie.jobs.successfulStatusCheck.rate")) .thenReturn(this.successfulCheckRate); Mockito.when(this.registry.counter("genie.jobs.timeout.rate")).thenReturn(this.timeoutRate); Mockito.when(this.registry.counter("genie.jobs.finished.rate")).thenReturn(this.finishedRate); Mockito.when(this.registry.counter("genie.jobs.unsuccessfulStatusCheck.rate")) .thenReturn(this.unsuccessfulCheckRate); Mockito.when(this.registry.counter("genie.jobs.stdOutTooLarge.rate")).thenReturn(this.stdOutTooLarge); Mockito.when(this.registry.counter("genie.jobs.stdErrTooLarge.rate")).thenReturn(this.stdErrTooLarge); final JobsProperties outputMaxProperties = JobsProperties.getJobsPropertiesDefaults(); outputMaxProperties.getMax().setStdOutSize(MAX_STD_OUT_LENGTH); outputMaxProperties.getMax().setStdErrSize(MAX_STD_ERR_LENGTH); this.monitor = new JobMonitor(this.jobExecution, this.stdOut, this.stdErr, this.genieEventBus, this.registry, outputMaxProperties, this.processChecker); } /** * This test should only run on windows machines and asserts that the system fails on Windows. */ @Test(expected = UnsupportedOperationException.class) public void cantRunOnWindows() { Assume.assumeTrue(SystemUtils.IS_OS_WINDOWS); this.monitor.run(); } /** * Make sure that a process whose std out file has grown too large will attempt to be killed. * * @throws IOException on error */ @Test public void canKillProcessOnTooLargeStdOut() throws IOException { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); Mockito.when(this.executor.execute(Mockito.any(CommandLine.class))).thenReturn(0).thenReturn(0) .thenReturn(0); Mockito.when(this.stdOut.exists()).thenReturn(true); Mockito.when(this.stdOut.length()).thenReturn(MAX_STD_OUT_LENGTH - 1).thenReturn(MAX_STD_OUT_LENGTH) .thenReturn(MAX_STD_OUT_LENGTH + 1); Mockito.when(this.stdErr.exists()).thenReturn(false); for (int i = 0; i < 3; i++) { this.monitor.run(); } Mockito.verify(this.successfulCheckRate, Mockito.times(2)).increment(); Mockito.verify(this.stdOutTooLarge, Mockito.times(1)).increment(); Mockito.verify(this.genieEventBus, Mockito.times(1)) .publishSynchronousEvent(Mockito.any(KillJobEvent.class)); } /** * Make sure that a process whose std err file has grown too large will attempt to be killed. * * @throws IOException on error */ @Test public void canKillProcessOnTooLargeStdErr() throws IOException { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); Mockito.when(this.executor.execute(Mockito.any(CommandLine.class))).thenReturn(0).thenReturn(0) .thenReturn(0); Mockito.when(this.stdOut.exists()).thenReturn(false); Mockito.when(this.stdErr.exists()).thenReturn(true); Mockito.when(this.stdErr.length()).thenReturn(MAX_STD_ERR_LENGTH - 1).thenReturn(MAX_STD_ERR_LENGTH) .thenReturn(MAX_STD_ERR_LENGTH + 1); for (int i = 0; i < 3; i++) { this.monitor.run(); } Mockito.verify(this.successfulCheckRate, Mockito.times(2)).increment(); Mockito.verify(this.stdErrTooLarge, Mockito.times(1)).increment(); Mockito.verify(this.genieEventBus, Mockito.times(1)) .publishSynchronousEvent(Mockito.any(KillJobEvent.class)); } /** * Make sure that a running process doesn't publish anything. * * @throws Exception on error */ @Test public void canCheckRunningProcessOnUnixLikeSystem() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); Mockito.when(this.stdOut.exists()).thenReturn(false); Mockito.when(this.stdErr.exists()).thenReturn(false); for (int i = 0; i < 3; i++) { this.monitor.run(); } Mockito.verify(this.processChecker, Mockito.times(3)).checkProcess(); Mockito.verify(this.successfulCheckRate, Mockito.times(3)).increment(); Mockito.verify(this.genieEventBus, Mockito.never()) .publishSynchronousEvent(Mockito.any(ApplicationEvent.class)); Mockito.verify(this.genieEventBus, Mockito.never()) .publishAsynchronousEvent(Mockito.any(ApplicationEvent.class)); } /** * Make sure that a finished process sends event. * * @throws Exception on error */ @Test public void canCheckFinishedProcessOnUnixLikeSystem() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); Mockito.doThrow(new ExecuteException("done", 1)).when(processChecker).checkProcess(); this.monitor.run(); final ArgumentCaptor<JobFinishedEvent> captor = ArgumentCaptor.forClass(JobFinishedEvent.class); Mockito.verify(this.genieEventBus, Mockito.times(1)).publishAsynchronousEvent(captor.capture()); Assert.assertNotNull(captor.getValue()); final String jobId = this.jobExecution.getId().orElseThrow(IllegalArgumentException::new); Assert.assertThat(captor.getValue().getId(), Matchers.is(jobId)); Assert.assertThat(captor.getValue().getSource(), Matchers.is(this.monitor)); Mockito.verify(this.finishedRate, Mockito.times(1)).increment(); } /** * Make sure that a timed out process sends event. * @throws Exception in case of error */ @Test public void canTryToKillTimedOutProcess() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); // Set timeout to yesterday to force timeout when check happens final Instant yesterday = Instant.now().minus(1, ChronoUnit.DAYS); this.jobExecution = new JobExecution.Builder(UUID.randomUUID().toString()).withProcessId(3808) .withCheckDelay(DELAY).withTimeout(yesterday).withId(UUID.randomUUID().toString()).build(); this.monitor = new JobMonitor(this.jobExecution, this.stdOut, this.stdErr, this.genieEventBus, this.registry, JobsProperties.getJobsPropertiesDefaults(), processChecker); Mockito.doThrow(new GenieTimeoutException("...")).when(processChecker).checkProcess(); this.monitor.run(); final ArgumentCaptor<KillJobEvent> captor = ArgumentCaptor.forClass(KillJobEvent.class); Mockito.verify(this.genieEventBus, Mockito.times(1)).publishSynchronousEvent(captor.capture()); Assert.assertNotNull(captor.getValue()); final String jobId = this.jobExecution.getId().orElseThrow(IllegalArgumentException::new); Assert.assertThat(captor.getValue().getId(), Matchers.is(jobId)); Assert.assertThat(captor.getValue().getReason(), Matchers.is(JobStatusMessages.JOB_EXCEEDED_TIMEOUT)); Assert.assertThat(captor.getValue().getSource(), Matchers.is(this.monitor)); Mockito.verify(this.timeoutRate, Mockito.times(1)).increment(); } /** * Make sure that an error doesn't publish anything until it runs too many times then it tries to kill the job. * * @throws Exception on error */ @Test public void cantGetStatusIfErrorOnUnixLikeSystem() throws Exception { Assume.assumeTrue(SystemUtils.IS_OS_UNIX); Mockito.doThrow(new IOException()).when(processChecker).checkProcess(); // Run six times to force error for (int i = 0; i < 6; i++) { this.monitor.run(); } final ArgumentCaptor<KillJobEvent> eventCaptor = ArgumentCaptor.forClass(KillJobEvent.class); Mockito.verify(this.genieEventBus, Mockito.times(1)).publishSynchronousEvent(eventCaptor.capture()); final List<KillJobEvent> events = eventCaptor.getAllValues(); Assert.assertThat(events.size(), Matchers.is(1)); final String jobId = this.jobExecution.getId().orElseThrow(IllegalArgumentException::new); Assert.assertThat(events.get(0).getId(), Matchers.is(jobId)); Assert.assertThat(events.get(0).getSource(), Matchers.is(this.monitor)); final ArgumentCaptor<JobFinishedEvent> finishedCaptor = ArgumentCaptor.forClass(JobFinishedEvent.class); Mockito.verify(this.genieEventBus, Mockito.times(1)).publishAsynchronousEvent(finishedCaptor.capture()); Assert.assertThat(finishedCaptor.getValue().getId(), Matchers.is(jobId)); Assert.assertThat(finishedCaptor.getValue().getSource(), Matchers.is(this.monitor)); Mockito.verify(this.unsuccessfulCheckRate, Mockito.times(6)).increment(); } /** * Make sure the right schedule type is returned. */ @Test public void canGetScheduleType() { Assert.assertThat(this.monitor.getScheduleType(), Matchers.is(GenieTaskScheduleType.TRIGGER)); } /** * Make sure asking for a trigger is returns one. */ @Test public void canGetTrigger() { Assert.assertNotNull(this.monitor.getTrigger()); } /** * Make sure asking for a fixed rate isn't allowed. */ @Test(expected = UnsupportedOperationException.class) public void cantGetFixedRate() { this.monitor.getFixedRate(); } /** * Make sure asking for a fixed delay isn't allowed. */ @Test(expected = UnsupportedOperationException.class) public void cantGetFixedDelay() { Assert.assertThat(DELAY, Matchers.is(this.monitor.getFixedDelay())); } }