/******************************************************************************* * 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.ide.ext.machine.server.ssh; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.MachineRuntimeInfo; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; import org.eclipse.che.api.core.util.LineConsumer; import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.machine.shared.dto.event.MachineStatusEvent; import org.eclipse.che.api.ssh.server.SshManager; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.DockerConnectorProvider; import org.eclipse.che.plugin.docker.client.Exec; import org.eclipse.che.plugin.docker.client.LogMessage; import org.eclipse.che.plugin.docker.client.MessageProcessor; import org.eclipse.che.plugin.docker.client.params.CreateExecParams; import org.eclipse.che.plugin.docker.client.params.StartExecParams; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static org.eclipse.che.dto.server.DtoFactory.newDto; 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.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; /** * Test for {@link KeysInjector} * * @author Sergii Leschenko */ @Listeners(MockitoTestNGListener.class) public class KeysInjectorTest { private static final String WORKSPACE_ID = "workspace123"; private static final String MACHINE_ID = "machine123"; private static final String OWNER = "user123"; private static final String CONTAINER_ID = "container123"; private static final String EXEC_ID = "exec123"; @Captor ArgumentCaptor<EventSubscriber<MachineStatusEvent>> subscriberCaptor; @Captor ArgumentCaptor<MessageProcessor<LogMessage>> messageProcessorCaptor; @Mock Instance instance; @Mock MachineRuntimeInfo machineRuntime; @Mock Exec exec; @Mock LogMessage logMessage; @Mock LineConsumer lineConsumer; @Mock EventService eventService; @Mock DockerConnector docker; @Mock CheEnvironmentEngine environmentEngine; @Mock SshManager sshManager; @Mock User user; EventSubscriber<MachineStatusEvent> subscriber; private class MockConnectorProvider extends DockerConnectorProvider { public MockConnectorProvider() { super(Collections.emptyMap(), "default"); } @Override public DockerConnector get() { return docker; } } KeysInjector keysInjector; @BeforeMethod public void setUp() throws Exception { final Map<String, String> metadataProperties = new HashMap<>(); metadataProperties.put("id", CONTAINER_ID); when(machineRuntime.getProperties()).thenReturn(metadataProperties); when(environmentEngine.getMachine(WORKSPACE_ID, MACHINE_ID)).thenReturn(instance); when(instance.getOwner()).thenReturn(OWNER); when(instance.getRuntime()).thenReturn(machineRuntime); when(instance.getLogger()).thenReturn(lineConsumer); keysInjector = new KeysInjector(eventService, new MockConnectorProvider(), sshManager, environmentEngine); keysInjector.start(); verify(eventService).subscribe(subscriberCaptor.capture()); subscriber = subscriberCaptor.getValue(); when(docker.createExec(any(CreateExecParams.class))).thenReturn(exec); when(exec.getId()).thenReturn(EXEC_ID); } @Test public void shouldNotDoAnythingIfEventTypeDoesNotEqualToRunning() { subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.DESTROYING)); verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test public void shouldNotInjectSshKeysWhenThereAreNotAnyPair() throws Exception { when(sshManager.getPairs(anyString(), anyString())).thenReturn(Collections.emptyList()); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); verify(sshManager).getPairs(eq(OWNER), eq("machine")); verify(sshManager).getPair(eq(OWNER), eq("workspace"), eq(WORKSPACE_ID)); verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test public void shouldNotInjectSshKeysWhenThereAreNotAnyPairWithPublicKey() throws Exception { when(sshManager.getPairs(anyString(), anyString())) .thenReturn(Collections.singletonList(new SshPairImpl(OWNER, "machine", "myPair", null, null))); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); verify(sshManager).getPairs(eq(OWNER), eq("machine")); verify(sshManager).getPair(eq(OWNER), eq("workspace"), eq(WORKSPACE_ID)); verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test public void shouldInjectSshKeysWhenThereAreAnyPairWithNotNullPublicKey() throws Exception { when(sshManager.getPairs(anyString(), anyString())) .thenReturn(Arrays.asList(new SshPairImpl(OWNER, "machine", "myPair", "publicKey1", null), new SshPairImpl(OWNER, "machine", "myPair", "publicKey2", null))); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); verify(sshManager).getPairs(eq(OWNER), eq("machine")); verify(sshManager).getPair(eq(OWNER), eq("workspace"), eq(WORKSPACE_ID)); ArgumentCaptor<CreateExecParams> argumentCaptor = ArgumentCaptor.forClass(CreateExecParams.class); verify(docker).createExec(argumentCaptor.capture()); assertEquals(argumentCaptor.getValue().getCmd(), new String[] {"/bin/bash", "-c", "mkdir ~/.ssh/ -p" + "&& echo 'publicKey1' >> ~/.ssh/authorized_keys" + "&& echo 'publicKey2' >> ~/.ssh/authorized_keys"}); verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), anyObject()); verifyZeroInteractions(docker, environmentEngine, sshManager); } /** * Validate the usecase: There is a workspace sshkeypair but no machine keypair (empty list) * Expect that the workspace public key is injected. */ @Test public void shouldInjectSshKeysWhenThereIsOnlyWorkspaceKey() throws Exception { // no machine key pairs when(sshManager.getPairs(anyString(), eq("machine"))) .thenReturn(Collections.emptyList()); // workspace keypair when(sshManager.getPair(anyString(), eq("workspace"), anyString())) .thenReturn(new SshPairImpl(OWNER, "workspace", WORKSPACE_ID, "publicKeyWorkspace", null)); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); // check calls for machine and workspace ssh pairs verify(sshManager).getPairs(eq(OWNER), eq("machine")); verify(sshManager).getPair(eq(OWNER), eq("workspace"), eq(WORKSPACE_ID)); ArgumentCaptor<CreateExecParams> argumentCaptor = ArgumentCaptor.forClass(CreateExecParams.class); verify(docker).createExec(argumentCaptor.capture()); assertEquals(argumentCaptor.getValue().getCmd(), new String[] {"/bin/bash", "-c", "mkdir ~/.ssh/ -p" + "&& echo 'publicKeyWorkspace' >> ~/.ssh/authorized_keys"}); verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), anyObject()); verifyZeroInteractions(docker, environmentEngine, sshManager); } /** * Validate the usecase: There is a workspace sshkeypair (without public key) but there is a machine keypair * Expect that only the machine keypair is injected (as workspace keypair has no public key). */ @Test public void shouldInjectSshKeysWhenThereIsNoPublicWorkspaceKeyButMachineKeys() throws Exception { // no machine key pairs when(sshManager.getPairs(anyString(), eq("machine"))) .thenReturn(Arrays.asList(new SshPairImpl(OWNER, "machine", "myPair", "publicKey1", null))); // workspace keypair without public key when(sshManager.getPair(anyString(), eq("workspace"), anyString())) .thenReturn(new SshPairImpl(OWNER, "workspace", WORKSPACE_ID, null, null)); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); // check calls for machine and workspace ssh pairs verify(sshManager).getPairs(eq(OWNER), eq("machine")); verify(sshManager).getPair(eq(OWNER), eq("workspace"), eq(WORKSPACE_ID)); ArgumentCaptor<CreateExecParams> argumentCaptor = ArgumentCaptor.forClass(CreateExecParams.class); verify(docker).createExec(argumentCaptor.capture()); assertEquals(argumentCaptor.getValue().getCmd(), new String[] {"/bin/bash", "-c", "mkdir ~/.ssh/ -p" + "&& echo 'publicKey1' >> ~/.ssh/authorized_keys"}); verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), anyObject()); verifyZeroInteractions(docker, environmentEngine, sshManager); } /** * Validate the usecase of no workspace keypair (notfound exception) and no machine keypair * Expect no ssh keys are injected */ @Test public void shouldNotInjectSshKeysWhenThereIsNoWorkspaceKey() throws Exception { // no machine key pairs when(sshManager.getPairs(anyString(), eq("machine"))) .thenReturn(Collections.emptyList()); // no workspace keypair when(sshManager.getPair(anyString(), eq("workspace"), anyString())) .thenThrow(NotFoundException.class); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(environmentEngine).getMachine(eq(WORKSPACE_ID), eq(MACHINE_ID)); // check calls for machine and workspace ssh pairs verify(sshManager).getPairs(eq(OWNER), eq("machine")); verify(sshManager).getPair(eq(OWNER), eq("workspace"), eq(WORKSPACE_ID)); verifyZeroInteractions(docker, environmentEngine, sshManager); } @Test public void shouldSendMessageInMachineLoggerWhenSomeErrorOcursOnKeysInjection() throws Exception { when(sshManager.getPairs(anyString(), anyString())) .thenReturn(Collections.singletonList(new SshPairImpl(OWNER, "machine", "myPair", "publicKey1", null))); when(logMessage.getType()).thenReturn(LogMessage.Type.STDERR); when(logMessage.getContent()).thenReturn("FAILED"); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) .withWorkspaceId(WORKSPACE_ID)); verify(docker).startExec(eq(StartExecParams.create(EXEC_ID)), messageProcessorCaptor.capture()); final MessageProcessor<LogMessage> value = messageProcessorCaptor.getValue(); value.process(logMessage); verify(lineConsumer).writeLine(eq("Error of injection public ssh keys. FAILED")); } }