/******************************************************************************* * 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.plugin.docker.machine.cleaner; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.environment.server.CheEnvironmentEngine; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.api.workspace.server.WorkspaceRuntimes; import org.eclipse.che.plugin.docker.client.DockerConnector; import org.eclipse.che.plugin.docker.client.DockerConnectorProvider; import org.eclipse.che.plugin.docker.client.json.ContainerListEntry; import org.eclipse.che.plugin.docker.client.json.network.ContainerInNetwork; import org.eclipse.che.plugin.docker.client.json.network.Network; import org.eclipse.che.plugin.docker.client.params.RemoveContainerParams; import org.eclipse.che.plugin.docker.machine.DockerContainerNameGenerator; import org.mockito.Matchers; 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.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import static java.util.Arrays.asList; import static java.util.Optional.of; import static org.eclipse.che.plugin.docker.machine.DockerContainerNameGenerator.ContainerNameInfo; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; 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.when; /** * Test for {@link DockerAbandonedResourcesCleaner} * * @author Alexander Andrienko */ @Listeners(MockitoTestNGListener.class) public class DockerAbandonedResourcesCleanerTest { private static final String machineId1 = "machineid1"; private static final String workspaceId1 = "workspaceid1"; private static final String machineId2 = "machineid2"; private static final String workspaceId2 = "workspaceid2"; private static final String containerName1 = "containerName1"; private static final String containerId1 = "containerId1"; private static final String containerName2 = "containerName2"; private static final String containerId2 = "containerId2"; private static final String containerName3 = "containerName3"; private static final String containerId3 = "containerId3"; private static final String EXITED_STATUS = "exited"; private static final String RUNNING_STATUS = "Up 6 hour ago"; private static final String abandonedNetworkId = "abandonedNetworkId"; private static final String usedNetworkId = "usedNetworkId"; private static final String additionalNetworkId = "CheAdditionalNetworkId"; private static final String abandonedNetworkName = "workspace1234567890abcdef_1234567890abcdef"; private static final String usedNetworkName = "workspace0987654321zyxwvu_0987654321zyxwvu"; private static final String additionalNetworkName = "CheAdditionalNetwork"; @Mock private CheEnvironmentEngine environmentEngine; @Mock private DockerConnectorProvider dockerConnectorProvider; @Mock private DockerConnector dockerConnector; @Mock private DockerContainerNameGenerator nameGenerator; @Mock private WorkspaceRuntimes workspaceRuntimes; @Mock private Instance instance; @Mock private MachineImpl machineImpl1; @Mock private MachineImpl machineImpl2; @Mock private ContainerListEntry container1; @Mock private ContainerListEntry container2; @Mock private ContainerListEntry container3; @Mock private ContainerNameInfo containerNameInfo1; @Mock private ContainerNameInfo containerNameInfo2; @Mock private ContainerNameInfo containerNameInfo3; @Mock private Network abandonedNetwork; @Mock private Network usedNetwork; @Mock private Network additionalNetwork; @Mock private ContainerInNetwork containerInNetwork1; private List<Network> networks; private Map<String, ContainerInNetwork> abandonedNetworkContainers; private Map<String, ContainerInNetwork> usedNetworkContainers; private Map<String, ContainerInNetwork> additionalNetworkContainers = new HashMap<>(); private Set<Set<String>> additionalNetworks = new HashSet<>(); private Set<String> userNetworks = new HashSet<>(Arrays.asList(additionalNetworkName)); private DockerAbandonedResourcesCleaner cleaner; @BeforeMethod public void setUp() throws Exception { networks = new ArrayList<>(); abandonedNetworkContainers = new HashMap<>(); usedNetworkContainers = new HashMap<>(); when(dockerConnectorProvider.get()).thenReturn(dockerConnector); cleaner = spy(new DockerAbandonedResourcesCleaner(environmentEngine, dockerConnectorProvider, nameGenerator, workspaceRuntimes, additionalNetworks)); when(environmentEngine.getMachine(workspaceId1, machineId1)).thenReturn(instance); when(environmentEngine.getMachine(workspaceId2, machineId2)).thenThrow(new NotFoundException("test")); when(machineImpl1.getId()).thenReturn(machineId1); when(machineImpl1.getWorkspaceId()).thenReturn(workspaceId1); when(dockerConnector.listContainers()).thenReturn(asList(container1, container2, container3)); when(container1.getNames()).thenReturn(new String[] {containerName1}); when(container1.getStatus()).thenReturn(RUNNING_STATUS); when(container1.getId()).thenReturn(containerId1); when(container2.getNames()).thenReturn(new String[] {containerName2}); when(container2.getStatus()).thenReturn(RUNNING_STATUS); when(container2.getId()).thenReturn(containerId2); when(container3.getNames()).thenReturn(new String[] {containerName3}); when(container3.getStatus()).thenReturn(RUNNING_STATUS); when(container3.getId()).thenReturn(containerId3); when(nameGenerator.parse(containerName1)).thenReturn(of(containerNameInfo1)); when(nameGenerator.parse(containerName2)).thenReturn(of(containerNameInfo2)); when(nameGenerator.parse(containerName3)).thenReturn(of(containerNameInfo3)); when(containerNameInfo1.getMachineId()).thenReturn(machineId1); when(containerNameInfo1.getWorkspaceId()).thenReturn(workspaceId1); when(containerNameInfo2.getMachineId()).thenReturn(machineId2); when(containerNameInfo2.getWorkspaceId()).thenReturn(workspaceId2); when(containerNameInfo3.getMachineId()).thenReturn(machineId2); when(containerNameInfo3.getWorkspaceId()).thenReturn(workspaceId2); when(dockerConnector.getNetworks(any())).thenReturn(networks); when(abandonedNetwork.getId()).thenReturn(abandonedNetworkId); when(usedNetwork.getId()).thenReturn(usedNetworkId); when(additionalNetwork.getId()).thenReturn(abandonedNetworkId); when(abandonedNetwork.getName()).thenReturn(abandonedNetworkName); when(usedNetwork.getName()).thenReturn(usedNetworkName); when(additionalNetwork.getName()).thenReturn(abandonedNetworkName); when(abandonedNetwork.getContainers()).thenReturn(abandonedNetworkContainers); when(usedNetwork.getContainers()).thenReturn(usedNetworkContainers); when(additionalNetwork.getContainers()).thenReturn(additionalNetworkContainers); } @Test public void cleanerShouldRunCleanOfContainerAndThenCleanOfNetworks() { // when cleaner.run(); // then verify(cleaner).cleanContainers(); verify(cleaner).cleanNetworks(); } @Test public void cleanerShouldRunCleanNetworksEvenIfCleanOfContainersFailed() throws IOException { // given when(dockerConnector.listContainers()).thenThrow(new IOException("Error while fetching docker containers list")); // when cleaner.run(); // then verify(cleaner).cleanNetworks(); } @Test public void cleanerShouldKillAndRemoveContainerIfThisContainerIsRunningAndContainerNameInfoIsNotEmptyAndContainerIsNotExistInTheAPI() throws Exception { cleaner.cleanContainers(); verify(dockerConnector).listContainers(); verify(nameGenerator, times(3)).parse(anyString()); verify(environmentEngine, times(3)).getMachine(anyString(), anyString()); verify(dockerConnector, times(2)).killContainer(anyString()); verify(dockerConnector, times(2)).removeContainer(Matchers.anyObject()); verify(dockerConnector, never()).killContainer(containerId1); verify(dockerConnector, never()).removeContainer(RemoveContainerParams.create(containerId1).withForce(true).withRemoveVolumes(true)); } @Test public void cleanerShouldRemoveButShouldNotKillContainerWithStatusNotRunning() throws Exception { when(container2.getStatus()).thenReturn(EXITED_STATUS); cleaner.cleanContainers(); verify(dockerConnector, never()).killContainer(containerId2); verify(dockerConnector).removeContainer(RemoveContainerParams.create(containerId2).withForce(true).withRemoveVolumes(true)); } @Test public void cleanerShouldNotKillAndRemoveContainerIfMachineManagerDetectedExistingThisContainerInTheAPI() throws Exception { when(environmentEngine.getMachine(anyString(), anyString())).thenReturn(instance); cleaner.cleanContainers(); verify(dockerConnector, never()).killContainer(anyString()); verify(dockerConnector, never()).removeContainer(Matchers.anyObject()); } @Test public void cleanerShouldNotKillAndRemoveContainerIfContainerNameInfoIsEmpty() throws IOException { when(nameGenerator.parse(anyString())).thenReturn(Optional.empty()); cleaner.cleanContainers(); verify(dockerConnector, never()).killContainer(anyString()); verify(dockerConnector, never()).removeContainer(Matchers.anyObject()); } @Test public void shouldRemoveAbandonedNetwork() throws IOException { // given networks.add(abandonedNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector).removeNetwork(eq(abandonedNetworkId)); } @Test public void shouldNotRemoveNetworkIfItNameNotMatchCheNetworkPattern() throws IOException { // given when(abandonedNetwork.getName()).thenReturn("UserNetwork"); networks.add(abandonedNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector, never()).removeNetwork(eq(abandonedNetworkId)); } @Test public void shouldNotRemoveNetworkWhenItContainsContainer() throws IOException { // given usedNetworkContainers.put(containerId1, containerInNetwork1); networks.add(usedNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector, never()).removeNetwork(eq(usedNetworkId)); } @Test public void shouldNotRemoveNetworkWhichIsInWorkspaceRuntime() throws IOException { // given final String usedNetworkWorkspace = usedNetworkName.substring(0, 25); when(workspaceRuntimes.hasRuntime(usedNetworkWorkspace)).thenReturn(true); networks.add(usedNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector, never()).removeNetwork(eq(usedNetworkId)); } @Test public void shouldRemoveOnlyAbandonedNetworks() throws IOException { // given usedNetworkContainers.put(containerId1, containerInNetwork1); networks.add(abandonedNetwork); networks.add(usedNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector).removeNetwork(abandonedNetworkId); verify(dockerConnector, never()).removeNetwork(usedNetworkId); } @Test public void shouldNotRemoveNetworkIfItInAdditionalNetworksList() throws IOException { // given additionalNetworks.add(userNetworks); cleaner = spy(new DockerAbandonedResourcesCleaner(environmentEngine, dockerConnectorProvider, nameGenerator, workspaceRuntimes, additionalNetworks)); networks.add(additionalNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector, never()).removeNetwork(additionalNetworkId); } @Test public void shouldNotRemoveNetworkIfItInAdditionalNetworksListAndHasNameAsNetworkToRemove() throws IOException { // given final String additionalNetworkName = "workspace1additional1netw_ork1name4test148"; userNetworks = new HashSet<>(Arrays.asList(additionalNetworkName)); additionalNetworks.add(userNetworks); cleaner = spy(new DockerAbandonedResourcesCleaner(environmentEngine, dockerConnectorProvider, nameGenerator, workspaceRuntimes, additionalNetworks)); when(additionalNetwork.getName()).thenReturn(additionalNetworkName); networks.add(additionalNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector, never()).removeNetwork(additionalNetworkId); } @Test public void shouldRemoveAbandonedNetworkEvenIfRemovingOfPreviousOneFailed() throws IOException { // given doThrow(new IOException("Failed to remove docker network")).when(dockerConnector).removeNetwork(usedNetworkId); networks.add(abandonedNetwork); networks.add(usedNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector).removeNetwork(abandonedNetworkId); verify(dockerConnector).removeNetwork(usedNetworkId); } @Test public void shouldBeAbleToRemoveSeveralAbandonedNetworks() throws IOException { // given final Network abandonedNetwork2 = mock(Network.class); final String abandonedNetwork2Id = "network2"; when(abandonedNetwork2.getId()).thenReturn(abandonedNetwork2Id); when(abandonedNetwork2.getName()).thenReturn("workspace0w5kg95j93kd9a1l_cjmd8rbnf9j9dnso"); when(abandonedNetwork2.getContainers()).thenReturn(new HashMap<>()); final Network userNetwork = mock(Network.class); final String userNetworkId = "network4"; when(userNetwork.getId()).thenReturn(userNetworkId); when(userNetwork.getName()).thenReturn("userNetwork"); when(userNetwork.getContainers()).thenReturn(new HashMap<>()); usedNetworkContainers.put(containerId1, containerInNetwork1); networks.add(usedNetwork); networks.add(abandonedNetwork); networks.add(abandonedNetwork2); networks.add(userNetwork); // when cleaner.cleanNetworks(); // then verify(dockerConnector, never()).removeNetwork(usedNetworkId); verify(dockerConnector, never()).removeNetwork(userNetworkId); verify(dockerConnector).removeNetwork(abandonedNetworkId); verify(dockerConnector).removeNetwork(abandonedNetworkId); } }