Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix recovery of Devfile based workspaces #13321

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import com.google.inject.Inject;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
Expand All @@ -41,20 +40,16 @@
import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.model.workspace.WorkspaceConfig;
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
import org.eclipse.che.api.core.model.workspace.config.Environment;
import org.eclipse.che.api.core.model.workspace.devfile.Devfile;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
import org.eclipse.che.api.workspace.shared.event.WorkspaceCreatedEvent;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.annotation.Traced;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.commons.tracing.TracingTags;
import org.slf4j.Logger;
Expand All @@ -78,7 +73,6 @@ public class WorkspaceManager {
private final AccountManager accountManager;
private final EventService eventService;
private final WorkspaceValidator validator;
private final DevfileToWorkspaceConfigConverter devfileConverter;

@Inject
public WorkspaceManager(
Expand All @@ -93,7 +87,6 @@ public WorkspaceManager(
this.accountManager = accountManager;
this.eventService = eventService;
this.validator = validator;
this.devfileConverter = devfileConverter;
}

/**
Expand Down Expand Up @@ -405,24 +398,17 @@ private void startAsync(
WorkspaceImpl workspace, @Nullable String envName, Map<String, String> options)
throws ConflictException, NotFoundException, ServerException {

Optional<Pair<String, Environment>> validEnvOpt = getValidatedEnvironment(workspace, envName);

// Sidecar-based workspaces are allowed not to have environments
Pair<String, Environment> environment = null;
if (validEnvOpt.isPresent()) {
environment = validEnvOpt.get();
try {
runtimes.validate(workspace, envName);
} catch (ValidationException e) {
throw new ConflictException(e.getMessage(), e);
}

workspace.getAttributes().put(UPDATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis()));
workspaceDao.update(workspace);

runtimes
.startAsync(
workspace,
environment,
getCommands(workspace),
getAttributes(workspace),
firstNonNull(options, Collections.emptyMap()))
.startAsync(workspace, envName, firstNonNull(options, Collections.emptyMap()))
.thenAccept(aVoid -> handleStartupSuccess(workspace))
.exceptionally(
ex -> {
Expand All @@ -435,69 +421,6 @@ private void startAsync(
});
}

private Optional<Pair<String, Environment>> getValidatedEnvironment(
WorkspaceImpl workspace, @Nullable String envName) throws NotFoundException, ServerException {
WorkspaceConfig config = workspace.getConfig();

if (workspace.getDevfile() != null) {
config = devfileConverter.convert(workspace.getDevfile());
}

if (envName != null && !config.getEnvironments().containsKey(envName)) {
throw new NotFoundException(
format(
"Workspace '%s:%s' doesn't contain environment '%s'",
workspace.getNamespace(), config.getName(), envName));
}

envName = firstNonNull(envName, config.getDefaultEnv());

if (envName == null
&& SidecarToolingWorkspaceUtil.isSidecarBasedWorkspace(config.getAttributes())) {
// Sidecar-based workspaces are allowed not to have any environments
return Optional.empty();
}

// validate environment in advance
if (envName == null) {
throw new NotFoundException(
format(
"Workspace %s:%s can't use null environment",
workspace.getNamespace(), config.getName()));
}

Environment environment = config.getEnvironments().get(envName);
try {
runtimes.validate(environment);
} catch (InfrastructureException | ValidationException e) {
throw new ServerException(e);
}

return Optional.of(Pair.of(envName, new EnvironmentImpl(environment)));
}

private List<? extends org.eclipse.che.api.core.model.workspace.config.Command> getCommands(
WorkspaceImpl workspace) throws NotFoundException, ServerException {
WorkspaceConfig config = workspace.getConfig();

if (workspace.getDevfile() != null) {
config = devfileConverter.convert(workspace.getDevfile());
}

return config.getCommands();
}

private Map<String, String> getAttributes(WorkspaceImpl workspace)
throws NotFoundException, ServerException {
WorkspaceConfig config = workspace.getConfig();

if (workspace.getDevfile() != null) {
config = devfileConverter.convert(workspace.getDevfile());
}

return config.getAttributes();
}

/** Returns first non-null argument or null if both are null. */
private <T> T firstNonNull(T first, T second) {
return first != null ? first : second;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
import org.eclipse.che.commons.annotation.Traced;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.lang.concurrent.ThreadLocalPropagateContext;
import org.eclipse.che.commons.lang.concurrent.Unlocker;
import org.eclipse.che.commons.subject.Subject;
Expand Down Expand Up @@ -113,6 +112,7 @@ public class WorkspaceRuntimes {
private final Map<String, InternalEnvironmentFactory> environmentFactories;
private final RuntimeInfrastructure infrastructure;
private final ProbeScheduler probeScheduler;
private final DevfileToWorkspaceConfigConverter devfileConverter;
// Unique identifier for this workspace runtimes
private final String workspaceRuntimesId;

Expand All @@ -127,7 +127,8 @@ public class WorkspaceRuntimes {
@SuppressWarnings("unused") DBInitializer ignored,
ProbeScheduler probeScheduler,
WorkspaceStatusCache statuses,
WorkspaceLockService lockService) {
WorkspaceLockService lockService,
DevfileToWorkspaceConfigConverter devfileConverter) {
this(
eventService,
envFactories,
Expand All @@ -137,7 +138,8 @@ public class WorkspaceRuntimes {
ignored,
probeScheduler,
statuses,
lockService);
lockService,
devfileConverter);
this.runtimes = runtimes;
}

Expand All @@ -151,7 +153,8 @@ public WorkspaceRuntimes(
@SuppressWarnings("unused") DBInitializer ignored,
ProbeScheduler probeScheduler,
WorkspaceStatusCache statuses,
WorkspaceLockService lockService) {
WorkspaceLockService lockService,
DevfileToWorkspaceConfigConverter devfileConverter) {
this.probeScheduler = probeScheduler;
this.runtimes = new ConcurrentHashMap<>();
this.statuses = statuses;
Expand All @@ -162,6 +165,7 @@ public WorkspaceRuntimes(
this.infrastructure = infra;
this.environmentFactories = ImmutableMap.copyOf(envFactories);
this.lockService = lockService;
this.devfileConverter = devfileConverter;
LOG.info("Configured factories for environments: '{}'", envFactories.keySet());
LOG.info("Registered infrastructure '{}'", infra.getName());
SetView<String> notSupportedByInfra =
Expand Down Expand Up @@ -193,14 +197,73 @@ private static RuntimeImpl asRuntime(InternalRuntime<?> runtime) throws ServerEx
}
}

public void validate(Environment environment)
throws NotFoundException, InfrastructureException, ValidationException {
/**
* Validates the specified workspace configuration.
*
* <p>The typical usage of this method is performing validating before asynchronous start of
* workspace.
*
* @param workspace workspace config that contains environment or devfile to start
* @param envName environment to start. Default will be used if null.
* @throws NotFoundException if workspace does not have environment with the specified name
* @throws NotFoundException if workspace has environment with type that is not supported
* @throws ValidationException if environment is not a valid and can not be run
* @throws ServerException if any other issue occurred during validation
*/
public void validate(WorkspaceImpl workspace, @Nullable String envName)
throws ValidationException, NotFoundException, ServerException {
WorkspaceConfig config = workspace.getConfig();

if (workspace.getDevfile() != null) {
config = devfileConverter.convert(workspace.getDevfile());
}

if (envName != null && !config.getEnvironments().containsKey(envName)) {
throw new NotFoundException(
format(
"Workspace '%s:%s' doesn't contain environment '%s'",
workspace.getNamespace(), config.getName(), envName));
}

if (envName == null) {
// use default environment if it is not defined
envName = config.getDefaultEnv();
}

if (envName == null
&& SidecarToolingWorkspaceUtil.isSidecarBasedWorkspace(config.getAttributes())) {
// Sidecar-based workspaces are allowed not to have any environments
return;
}

// validate environment in advance
if (envName == null) {
throw new NotFoundException(
format(
"Workspace %s:%s can't use null environment",
workspace.getNamespace(), config.getName()));
}

Environment environment = config.getEnvironments().get(envName);

if (environment == null) {
throw new NotFoundException(
format(
"Workspace does not have environment with name %s that specified to be run",
envName));
}

String type = environment.getRecipe().getType();
if (!infrastructure.getRecipeTypes().contains(type)) {
throw new NotFoundException("Infrastructure not found for type: " + type);
}

// try to create internal environment to check if the specified environment is valid
createInternalEnvironment(environment, emptyMap(), emptyList());
try {
createInternalEnvironment(environment, emptyMap(), emptyList());
} catch (InfrastructureException e) {
throw new ServerException(e.getMessage(), e);
}
}

/**
Expand Down Expand Up @@ -310,7 +373,7 @@ public WorkspaceStatus getStatus(String workspaceId) {
* WorkspaceStatus#STARTING} status.
*
* @param workspace workspace which environment should be started
* @param environment optional environment to run
* @param envName optional environment name to run
* @param options whether machines should be recovered(true) or not(false)
* @return completable future of start execution.
* @throws ConflictException when workspace is already running
Expand All @@ -322,11 +385,7 @@ public WorkspaceStatus getStatus(String workspaceId) {
*/
@Traced
public CompletableFuture<Void> startAsync(
WorkspaceImpl workspace,
@Nullable Pair<String, Environment> environment,
List<? extends Command> commands,
Map<String, String> attributes,
Map<String, String> options)
WorkspaceImpl workspace, @Nullable String envName, Map<String, String> options)
throws ConflictException, NotFoundException, ServerException {
TracingTags.WORKSPACE_ID.set(workspace.getId());
TracingTags.STACK_ID.set(() -> workspace.getAttributes().getOrDefault("stackId", "no stack"));
Expand All @@ -340,14 +399,21 @@ public CompletableFuture<Void> startAsync(
workspace.getName()));
}

WorkspaceConfig config = workspace.getConfig();
if (config == null) {
config = devfileConverter.convert(workspace.getDevfile());
}

if (envName == null) {
envName = config.getDefaultEnv();
}

final String ownerId = EnvironmentContext.getCurrent().getSubject().getUserId();
final RuntimeIdentity runtimeId =
new RuntimeIdentityImpl(
workspaceId, environment == null ? null : environment.first, ownerId);
final RuntimeIdentity runtimeId = new RuntimeIdentityImpl(workspaceId, envName, ownerId);
try {
InternalEnvironment internalEnv =
createInternalEnvironment(
environment == null ? null : environment.second, attributes, commands);
config.getEnvironments().get(envName), config.getAttributes(), config.getCommands());

RuntimeContext runtimeContext = infrastructure.prepare(runtimeId, internalEnv);
InternalRuntime runtime = runtimeContext.getRuntime();
Expand Down Expand Up @@ -585,6 +651,10 @@ InternalRuntime<?> recoverOne(RuntimeInfrastructure infra, RuntimeIdentity ident

Environment environment = null;
WorkspaceConfig workspaceConfig = workspace.getConfig();
if (workspaceConfig == null) {
workspaceConfig = devfileConverter.convert(workspace.getDevfile());
}

if (identity.getEnvName() != null) {
environment = workspaceConfig.getEnvironments().get(identity.getEnvName());
if (environment == null) {
Expand Down
Loading