CHE-1276: Add exception handler during creation machine from snapshot

6.19.x
Anton Korneta 2016-06-24 20:16:57 +03:00
parent 484f41867f
commit ec461af679
9 changed files with 181 additions and 21 deletions

View File

@ -21,6 +21,7 @@ import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.json.JsonHelper;
import org.eclipse.che.commons.json.JsonNameConvention;
import org.eclipse.che.commons.json.JsonParseException;
import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.lang.TarUtils;
import org.eclipse.che.commons.lang.ws.rs.ExtMediaType;
@ -29,6 +30,8 @@ import org.eclipse.che.plugin.docker.client.connection.DockerConnection;
import org.eclipse.che.plugin.docker.client.connection.DockerConnectionFactory;
import org.eclipse.che.plugin.docker.client.connection.DockerResponse;
import org.eclipse.che.plugin.docker.client.dto.AuthConfigs;
import org.eclipse.che.plugin.docker.client.exception.DockerException;
import org.eclipse.che.plugin.docker.client.exception.ImageNotFoundException;
import org.eclipse.che.plugin.docker.client.json.ContainerCommitted;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.ContainerCreated;
@ -375,7 +378,8 @@ public class DockerConnector {
* @deprecated use {@link #tag(TagParams)} instead
*/
@Deprecated
public void tag(String image, String repository, String tag) throws IOException {
public void tag(String image, String repository, String tag) throws ImageNotFoundException,
IOException {
tag(TagParams.create(image, repository).withTag(tag), dockerDaemonUri);
}
@ -1242,21 +1246,29 @@ public class DockerConnector {
* @deprecated use {@link #tag(TagParams)}
*/
@Deprecated
protected void doTag(String image, String repository, String tag, URI dockerDaemonUri) throws IOException {
protected void doTag(String image,
String repository,
String tag,
URI dockerDaemonUri) throws ImageNotFoundException,
IOException {
tag(TagParams.create(image, repository).withTag(tag), dockerDaemonUri);
}
/**
* Tag the docker image into a repository.
*
* @throws ImageNotFoundException
* when docker api return 404 status
* @throws IOException
* when a problem occurs with docker api calls
* when a problem occurs with docker api calls
*/
public void tag(final TagParams params) throws IOException {
public void tag(final TagParams params) throws ImageNotFoundException,
IOException {
tag(params, dockerDaemonUri);
}
private void tag(final TagParams params, URI dockerDaemonUri) throws IOException {
private void tag(final TagParams params, URI dockerDaemonUri) throws ImageNotFoundException,
IOException {
try (DockerConnection connection = connectionFactory.openConnection(dockerDaemonUri)
.method("POST")
.path("/images/" + params.getImage() + "/tag")
@ -1264,7 +1276,9 @@ public class DockerConnector {
addQueryParamIfNotNull(connection, "force", params.isForce());
addQueryParamIfNotNull(connection, "tag", params.getTag());
final DockerResponse response = connection.request();
if (response.getStatus() / 100 != 2) {
if (response.getStatus() == 404) {
throw new ImageNotFoundException(IoUtil.readStream(response.getInputStream()));
} else if (response.getStatus() / 100 != 2) {
throw getDockerException(response);
}
}

View File

@ -8,14 +8,14 @@
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.docker.client;
package org.eclipse.che.plugin.docker.client.exception;
import java.io.IOException;
/**
* @author andrew00x
*/
public final class DockerException extends IOException {
public class DockerException extends IOException {
private final int status;
private final String originError;

View File

@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2012-2016 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.client.exception;
/**
* Occurs when docker image is not found.
*
* @author Anton Korneta
*/
public class ImageNotFoundException extends DockerException {
public ImageNotFoundException(String message) {
super(message, 404);
}
}

View File

@ -23,6 +23,7 @@ import org.eclipse.che.plugin.docker.client.connection.DockerConnectionFactory;
import org.eclipse.che.plugin.docker.client.connection.DockerResponse;
import org.eclipse.che.plugin.docker.client.dto.AuthConfig;
import org.eclipse.che.plugin.docker.client.dto.AuthConfigs;
import org.eclipse.che.plugin.docker.client.exception.DockerException;
import org.eclipse.che.plugin.docker.client.json.ContainerCommitted;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.ContainerCreated;

View File

@ -28,6 +28,7 @@ import org.eclipse.che.api.core.util.SystemInfo;
import org.eclipse.che.api.machine.server.exception.InvalidRecipeException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProvider;
@ -38,11 +39,12 @@ import org.eclipse.che.commons.lang.IoUtil;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.DockerConnectorConfiguration;
import org.eclipse.che.plugin.docker.client.DockerFileException;
import org.eclipse.che.plugin.docker.client.UserSpecificDockerRegistryCredentialsProvider;
import org.eclipse.che.plugin.docker.client.Dockerfile;
import org.eclipse.che.plugin.docker.client.DockerfileParser;
import org.eclipse.che.plugin.docker.client.ProgressLineFormatterImpl;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.UserSpecificDockerRegistryCredentialsProvider;
import org.eclipse.che.plugin.docker.client.exception.ImageNotFoundException;
import org.eclipse.che.plugin.docker.client.json.ContainerConfig;
import org.eclipse.che.plugin.docker.client.json.HostConfig;
import org.eclipse.che.plugin.docker.client.params.PullParams;
@ -269,8 +271,12 @@ public class DockerInstanceProvider implements InstanceProvider {
* if other error occurs
*/
@Override
public Instance createInstance(final Machine machine, final LineConsumer creationLogsOutput)
throws UnsupportedRecipeException, InvalidRecipeException, NotFoundException, MachineException {
public Instance createInstance(Machine machine,
LineConsumer creationLogsOutput) throws UnsupportedRecipeException,
InvalidRecipeException,
SourceNotFoundException,
NotFoundException,
MachineException {
// based on machine source, do the right steps
MachineConfig machineConfig = machine.getConfig();
@ -314,8 +320,11 @@ public class DockerInstanceProvider implements InstanceProvider {
creationLogsOutput);
}
protected Instance createInstanceFromImage(final Machine machine, String machineContainerName,
final LineConsumer creationLogsOutput) throws NotFoundException, MachineException {
protected Instance createInstanceFromImage(Machine machine,
String machineContainerName,
LineConsumer creationLogsOutput) throws NotFoundException,
MachineException,
SourceNotFoundException {
final DockerMachineSource dockerMachineSource = new DockerMachineSource(machine.getConfig().getSource());
if (snapshotUseRegistry) {
@ -327,8 +336,10 @@ public class DockerInstanceProvider implements InstanceProvider {
try {
// tag image with generated name to allow sysadmin recognize it
docker.tag(TagParams.create(fullNameOfPulledImage, machineImageName));
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
} catch (ImageNotFoundException nfEx) {
throw new SourceNotFoundException(nfEx.getLocalizedMessage(), nfEx);
} catch (IOException ioEx) {
LOG.error(ioEx.getLocalizedMessage(), ioEx);
throw new MachineException("Can't create machine from snapshot.");
}
try {

View File

@ -33,10 +33,12 @@ import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.event.InstanceStateEvent;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException;
import org.eclipse.che.api.machine.server.model.impl.LimitsImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
@ -277,6 +279,7 @@ public class MachineManager {
ConflictException,
BadRequestException,
MachineException {
final MachineSourceImpl sourceCopy = machineConfig.getSource();
final InstanceProvider instanceProvider = machineInstanceProviders.getProvider(machineConfig.getType());
// Backward compatibility for source type 'Recipe'.
@ -329,10 +332,25 @@ public class MachineManager {
.getOutput());
try {
machineRegistry.addMachine(machine);
instanceCreator.createInstance(instanceProvider, machine, machineLogger);
try {
machineRegistry.addMachine(machine);
instanceCreator.createInstance(instanceProvider, machine, machineLogger);
} catch (MachineException ex) {
if (snapshot == null) {
throw ex;
}
if (ex.getCause() instanceof SourceNotFoundException) {
final LineConsumer logger = getMachineLogger(machineId,
getMachineChannels(machine.getConfig().getName(),
machine.getWorkspaceId(),
machine.getEnvName()).getOutput());
LOG.error("Image of snapshot for machine " + machineConfig.getName() + " not found. " +
"Machine will be created from origin source");
machine.getConfig().setSource(sourceCopy);
machineRegistry.addMachine(machine);
instanceCreator.createInstance(instanceProvider, machine, logger);
}
}
return machine;
} catch (ConflictException e) {
try {

View File

@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2012-2016 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.machine.server.exception;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
/**
* Occurs when machine sources is not found or is not available.
*
* @author Anton Korneta
*/
public class SourceNotFoundException extends MachineException {
public SourceNotFoundException(String message) {
super(message);
}
public SourceNotFoundException(ServiceError serviceError) {
super(serviceError);
}
public SourceNotFoundException(Throwable cause) {
super(cause);
}
public SourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -18,6 +18,7 @@ import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.machine.server.exception.InvalidRecipeException;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SnapshotException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.exception.UnsupportedRecipeException;
import java.util.Set;
@ -65,6 +66,7 @@ public interface InstanceProvider {
Instance createInstance(Machine machine,
LineConsumer creationLogsOutput) throws UnsupportedRecipeException,
InvalidRecipeException,
SourceNotFoundException,
NotFoundException,
MachineException;

View File

@ -22,11 +22,13 @@ import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.machine.server.dao.SnapshotDao;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.exception.SourceNotFoundException;
import org.eclipse.che.api.machine.server.model.impl.LimitsImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineImpl;
import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl;
import org.eclipse.che.api.machine.server.model.impl.ServerConfImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.recipe.RecipeImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.server.spi.InstanceProcess;
@ -61,6 +63,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
/**
@ -71,11 +74,15 @@ import static org.testng.Assert.assertNotNull;
*/
@Listeners(MockitoTestNGListener.class)
public class MachineManagerTest {
private static final int DEFAULT_MACHINE_MEMORY_SIZE_MB = 1000;
private static final String WS_ID = "testWsId";
private static final String ENVIRONMENT_NAME = "testEnvName";
private static final String USER_ID = "userId";
private static final String MACHINE_ID = "machineId";
private static final String NAMESPACE = "namespace";
private static final SubjectImpl CREATOR = new SubjectImpl("name", USER_ID, "token", false);
@Mock
private MachineInstanceProviders machineInstanceProviders;
@ -95,12 +102,13 @@ public class MachineManagerTest {
private InstanceProcess instanceProcess;
@Mock
private LineConsumer processLogger;
@Mock
private SnapshotDao snapshotDao;
private MachineManager manager;
@BeforeMethod
public void setUp() throws Exception {
final SnapshotDao snapshotDao = mock(SnapshotDao.class);
final EventService eventService = mock(EventService.class);
final String machineLogsDir = targetDir().resolve("logs-dir").toString();
IoUtil.deleteRecursive(new File(machineLogsDir));
@ -113,7 +121,7 @@ public class MachineManagerTest {
wsAgentLauncher));
EnvironmentContext envCont = new EnvironmentContext();
envCont.setSubject(new SubjectImpl(null, USER_ID, null, false));
envCont.setSubject(CREATOR);
EnvironmentContext.setCurrent(envCont);
RecipeImpl recipe = new RecipeImpl().withScript("script").withType("Dockerfile");
@ -123,6 +131,7 @@ public class MachineManagerTest {
when(machineInstanceProviders.getProvider(anyString())).thenReturn(instanceProvider);
HashSet<String> recipeTypes = new HashSet<>();
recipeTypes.add("test type 1");
recipeTypes.add("snapshot");
recipeTypes.add("dockerfile");
when(instanceProvider.getRecipeTypes()).thenReturn(recipeTypes);
when(instanceProvider.createInstance(any(Machine.class), any(LineConsumer.class))).thenReturn(instance);
@ -259,6 +268,40 @@ public class MachineManagerTest {
verify(machineLogger).close();
}
@Test
public void shouldCreateMachineFromOriginalSourceWhenMachineRecoverFails() throws Exception {
final SnapshotImpl snapshot = createSnapshot();
final MachineConfigImpl config = createMachineConfig();
when(manager.generateMachineId()).thenReturn(MACHINE_ID);
final MachineImpl machine = new MachineImpl(config,
MACHINE_ID,
WS_ID,
ENVIRONMENT_NAME,
CREATOR.getUserId(),
MachineStatus.CREATING,
null);
machine.getConfig().setSource(snapshot.getMachineSource());
when(snapshotDao.getSnapshot(WS_ID, ENVIRONMENT_NAME, config.getName())).thenReturn(snapshot);
when(instanceProvider.createInstance(eq(machine), any(LineConsumer.class))).thenThrow(new SourceNotFoundException(""));
when(machineRegistry.getMachine(MACHINE_ID)).thenReturn(machine);
final MachineImpl result = manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME);
machine.getConfig().setSource(config.getSource());
assertEquals(result, machine);
}
@Test(expectedExceptions = MachineException.class)
public void shouldThrowExceptionWhenMachineCreationFromOriginSourceFailed() throws Exception {
final MachineConfigImpl config = createMachineConfig();
when(manager.generateMachineId()).thenReturn(MACHINE_ID);
when(instanceProvider.createInstance(any(MachineImpl.class),
any(LineConsumer.class))).thenThrow(new MachineException(""));
manager.recoverMachine(config, WS_ID, ENVIRONMENT_NAME);
}
private void waitForExecutorIsCompletedTask() throws Exception {
for (int i = 0; ((ThreadPoolExecutor)manager.executor).getCompletedTaskCount() == 0 && i < 10; i++) {
Thread.sleep(300);
@ -287,4 +330,16 @@ public class MachineManagerTest {
"/some/path")),
Collections.singletonMap("key1", "value1"));
}
private SnapshotImpl createSnapshot() {
return SnapshotImpl.builder()
.setId("snapshot12")
.fromConfig(createMachineConfig())
.setWorkspaceId(WS_ID)
.setEnvName(ENVIRONMENT_NAME)
.setNamespace(NAMESPACE)
.setMachineSource(new MachineSourceImpl("snapshot").setLocation("location"))
.setDev(true)
.build();
}
}