/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.agent.server.launcher; import org.eclipse.che.api.agent.server.exception.AgentStartException; import org.eclipse.che.api.agent.shared.model.Agent; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.server.spi.InstanceNode; import org.eclipse.che.api.machine.server.spi.InstanceProcess; import org.mockito.Mock; import org.mockito.stubbing.Answer; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.ArrayList; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; /** * @author Alexander Garagatyi */ @Listeners(MockitoTestNGListener.class) public class AbstractAgentLauncherTest { @Mock private Instance machine; @Mock private Agent agent; @Mock private InstanceProcess process; @Mock private AgentLaunchingChecker agentChecker; private AbstractAgentLauncher launcher; @BeforeMethod public void setUp() throws Exception { launcher = spy(new TestAgentLauncher(500, 100, agentChecker)); when(agent.getScript()).thenReturn("script content"); doReturn(process).when(launcher).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenReturn(true); when(machine.getNode()).thenReturn(mock(InstanceNode.class)); } @Test public void shouldBeAbleToCheckAgentState() throws Exception { // when launcher.launch(machine, agent); // then verify(agentChecker).isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class)); } @Test public void doNothingIfAgentScriptIsNull() throws Exception { // given when(agent.getScript()).thenReturn(null); // when launcher.launch(machine, agent); // then verify(launcher, never()).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); verify(agent).getScript(); verifyNoMoreInteractions(agent); verifyZeroInteractions(machine); } @Test public void doNothingIfAgentScriptIsEmpty() throws Exception { // given when(agent.getScript()).thenReturn(""); // when launcher.launch(machine, agent); // then verify(launcher, never()).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); verify(agent).getScript(); verifyNoMoreInteractions(agent); verifyZeroInteractions(machine); } @Test public void shouldCheckIfAgentIsLaunchedUntilItIsLaunched() throws Exception { // given when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenReturn(false) .thenReturn(false) .thenReturn(false) .thenReturn(false) .thenReturn(true); // when launcher.launch(machine, agent); // then verify(agentChecker, times(5)).isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class)); } @Test(expectedExceptions = AgentStartException.class, expectedExceptionsMessageRegExp = "Fail launching agent .*. Workspace ID:.*") public void shouldNotCheckIfAgentIsLaunchedMoreThanAgentMaxStartTime() throws Exception { // given launcher = spy(new TestAgentLauncher(200, 100, agentChecker)); doReturn(process).when(launcher).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenReturn(false) .thenReturn(false) .thenReturn(false) .thenReturn(false) .thenReturn(true); // when launcher.launch(machine, agent); // then // ensure that isLaunched was called several times and then max pinging time was exceeded verify(agentChecker, atLeast(2)).isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class)); } @Test public void shouldNotCheckMoreFrequentThanAgentCheckDelay() throws Exception { // given launcher = spy(new TestAgentLauncher(200, 10, agentChecker)); doReturn(process).when(launcher).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); // record time of each check of agent state ArrayList<Long> checkTimestamps = new ArrayList<>(5); Answer<Boolean> recordTimestampAndReturnFalse = invocationOnMock -> { checkTimestamps.add(System.currentTimeMillis()); return false; }; Answer<Boolean> recordTimestampAndReturnTrue = invocationOnMock -> { checkTimestamps.add(System.currentTimeMillis()); return true; }; when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenAnswer(recordTimestampAndReturnFalse) .thenAnswer(recordTimestampAndReturnFalse) .thenAnswer(recordTimestampAndReturnFalse) .thenAnswer(recordTimestampAndReturnFalse) .thenAnswer(recordTimestampAndReturnTrue); // when launcher.launch(machine, agent); // then // ensure that each check was done after required timeout for (int i = 1; i < checkTimestamps.size(); i++) { assertTrue(checkTimestamps.get(i) - checkTimestamps.get(i - 1) >= 10); } } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "agent launcher test exception") public void shouldThrowServerExceptionIfMachineExceptionIsThrownByAgentCheck() throws Exception { // given when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))) .thenThrow(new MachineException("agent launcher test exception")); // when launcher.launch(machine, agent); } @Test public void shouldSetBackInterruptedFlagIfThreadWasInterrupted() throws Exception { try { // imitate interruption of launching thread when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenAnswer(invocationOnMock -> { Thread.currentThread().interrupt(); return false; }); // when launcher.launch(machine, agent); } catch (ServerException e) { // Ensure that after exiting launcher thread is still in interrupted state assertTrue(Thread.currentThread().isInterrupted()); } finally { // cleanup interrupted state Thread.interrupted(); } } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "Launching agent .* is interrupted") public void shouldThrowServerExceptionIfAgentCheckWasInterrupted() throws Exception { try { when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenAnswer(invocationOnMock -> { Thread.currentThread().interrupt(); return false; }); // when launcher.launch(machine, agent); } finally { // cleanup interrupted state Thread.interrupted(); } } @Test public void shouldStartMachineProcessWithAgentScriptExecution() throws Exception { // given String agentId = "testAgentId"; String agentScript = "testAgentScript"; when(agent.getId()).thenReturn(agentId); when(agent.getScript()).thenReturn(agentScript); when(launcher.start(any(Instance.class), any(Agent.class), any(LineConsumer.class))).thenCallRealMethod(); // when launcher.launch(machine, agent); // then verify(machine).createProcess(eq(new CommandImpl(agentId, agentScript, "agent")), eq(null)); } @Test(expectedExceptions = AgentStartException.class, expectedExceptionsMessageRegExp = "Fail launching agent .*\\. Workspace ID:.*") public void shouldLogAgentStartLogsIfTimeoutReached() throws Exception { // given launcher = spy(new TestAgentLauncher(-1, 100, agentChecker)); when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenReturn(false); doReturn(process).when(launcher).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); // when try { launcher.launch(machine, agent); fail("Should throw AgentStartException"); } catch (AgentStartException e) { // then verify(launcher).logAsErrorAgentStartLogs(anyObject(), anyString(), anyString()); // rethrow exception to verify message throw e; } } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "An error on agent start") public void shouldLogAgentStartLogsIfMachineExceptionOccurs() throws Exception { // given doThrow(new MachineException("An error on agent start")) .when(launcher).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); // when try { launcher.launch(machine, agent); fail("Should throw ServerException"); } catch (ServerException e) { // then verify(launcher).logAsErrorAgentStartLogs(anyObject(), anyString(), anyString()); // rethrow exception to verify message throw e; } } @Test(expectedExceptions = ServerException.class, expectedExceptionsMessageRegExp = "An error on process kill") public void shouldLogAgentStartLogsIfMachineExceptionOccursAfterAgentStartTimeoutHadReached() throws Exception { // given launcher = spy(new TestAgentLauncher(-1, 100, agentChecker)); when(agentChecker.isLaunched(any(Agent.class), any(InstanceProcess.class), any(Instance.class))).thenReturn(false); doReturn(process).when(launcher).start(any(Instance.class), any(Agent.class), any(LineConsumer.class)); doThrow(new MachineException("An error on process kill")).when(process).kill(); // when try { launcher.launch(machine, agent); fail("Should throw ServerException"); } catch (ServerException e) { // then verify(launcher).logAsErrorAgentStartLogs(anyObject(), anyString(), anyString()); // rethrow exception to verify message throw e; } } private static class TestAgentLauncher extends AbstractAgentLauncher { public TestAgentLauncher(long agentMaxStartTimeMs, long agentPingDelayMs, AgentLaunchingChecker agentLaunchingChecker) { super(agentMaxStartTimeMs, agentPingDelayMs, agentLaunchingChecker); } @Override protected InstanceProcess start(Instance machine, Agent agent, LineConsumer lineConsumer) throws ServerException { return super.start(machine, agent, lineConsumer); } @Override public String getAgentId() { return "testAgentId"; } @Override public String getMachineType() { return "testMachineType"; } } }