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

Prepare environment for async storage of project sources #16972

Merged
merged 12 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from 11 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 @@ -330,7 +330,7 @@ che.infra.kubernetes.pvc.strategy=common
che.infra.kubernetes.pvc.precreate_subpaths=true

# Defines the settings of PVC name for che workspaces.
# Each PVC strategy suplies this value differently.
# Each PVC strategy supplies this value differently.
# See doc for che.infra.kubernetes.pvc.strategy property
che.infra.kubernetes.pvc.name=claim-che-workspace

Expand Down Expand Up @@ -615,3 +615,11 @@ che.workspace.devfile.default_editor.plugins=eclipse/che-machine-exec-plugin/nig
# which will be mount into workspace containers as a files or env variables.
# Only secrets that match ALL given labels will be selected.
che.workspace.provision.secret.labels=app.kubernetes.io/part-of=che.eclipse.org,app.kubernetes.io/component=workspace-secret


# Plugin is added in case async storage feature will be enabled in workspace config
# and supported by environment
che.workspace.devfile.async.storage.plugin=eclipse/che-async-pv-plugin/nightly

# Docker image for the Che async storage
che.infra.kubernetes.async.storage.image=quay.io/eclipse/che-workspace-data-sync-storage:latest
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public final class Constants {
/** The label that contains a value with workspace id to which object belongs to. */
public static final String CHE_WORKSPACE_ID_LABEL = "che.workspace_id";

/** The label that contains a value with user id to which object belongs to. */
public static final String CHE_USER_ID_LABEL = "che.user_id";

/** The label that contains name of deployment responsible for Pod. */
public static final String CHE_DEPLOYMENT_NAME_LABEL = "che.deployment_name";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;

import static java.lang.Boolean.parseBoolean;
import static org.eclipse.che.api.workspace.shared.Constants.ASYNC_PERSIST_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.EphemeralWorkspaceUtility.isEphemeral;

import io.fabric8.kubernetes.api.model.PodSpec;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
Expand All @@ -31,6 +36,14 @@
*/
public class PodTerminationGracePeriodProvisioner implements ConfigurationProvisioner {
private final long graceTerminationPeriodSec;
/**
* This value will activate if workspace configured to use Async Storage. We can't set default
* grace termination period because we need to give some time on workspace stop action for backup
* changes to the persistent storage. At the moment no way to predict this time because it depends
* on amount of files, size of files and network ability. This is some empirical number of seconds
* which should be enough for most projects.
*/
private final long graceTerminationPeriodAsyncPvc = 60;

@Inject
public PodTerminationGracePeriodProvisioner(
Expand All @@ -48,7 +61,7 @@ public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)

for (PodData pod : k8sEnv.getPodsData().values()) {
if (!isTerminationGracePeriodSet(pod.getSpec())) {
pod.getSpec().setTerminationGracePeriodSeconds(graceTerminationPeriodSec);
pod.getSpec().setTerminationGracePeriodSeconds(getGraceTerminationPeriodSec(k8sEnv));
}
}
}
Expand All @@ -60,4 +73,12 @@ public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)
private boolean isTerminationGracePeriodSet(final PodSpec podSpec) {
return podSpec.getTerminationGracePeriodSeconds() != null;
}

private long getGraceTerminationPeriodSec(KubernetesEnvironment k8sEnv) {
Map<String, String> attributes = k8sEnv.getAttributes();
if (isEphemeral(attributes) && parseBoolean(attributes.get(ASYNC_PERSIST_ATTRIBUTE))) {
return graceTerminationPeriodAsyncPvc;
}
return graceTerminationPeriodSec;
}
}
8 changes: 8 additions & 0 deletions infrastructures/openshift/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-model</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-ssh</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-ssh-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-system</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.openshift;

import static java.lang.Boolean.parseBoolean;
import static java.lang.String.format;
import static org.eclipse.che.api.workspace.shared.Constants.ASYNC_PERSIST_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy.COMMON_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.EphemeralWorkspaceUtility.isEphemeral;

import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.workspace.server.WorkspaceAttributeValidator;
import org.eclipse.che.commons.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Validates the workspace attributes before workspace creation and updating if async storage
* configure.
*
* <p>To be valid for async storage workspace MUST have attributes:
*
* <ul>
* <li>{@link org.eclipse.che.api.workspace.shared.Constants#ASYNC_PERSIST_ATTRIBUTE} = 'true'
* <li>{@link org.eclipse.che.api.workspace.shared.Constants#PERSIST_VOLUMES_ATTRIBUTE} = 'false'
* </ul>
*
* <p>If set only {@link org.eclipse.che.api.workspace.shared.Constants#ASYNC_PERSIST_ATTRIBUTE} =
* 'true' {@link ValidationException} thrown.
*
* <p>If system configure with other value of properties than below {@link ValidationException}
* thrown.
*
* <ul>
* <li>che.infra.kubernetes.namespace.default=<username>-che
* <li>che.infra.kubernetes.namespace.allow_user_defined=false
* <li>che.infra.kubernetes.pvc.strategy=common
* <li>che.limits.user.workspaces.run.count=1
* </ul>
*/
public class AsyncStorageModeValidator implements WorkspaceAttributeValidator {

private static final Logger LOG = LoggerFactory.getLogger(AsyncStorageModeValidator.class);

private final String pvcStrategy;
private final boolean allowUserDefinedNamespaces;
private final String defaultNamespaceName;
private final int runtimesPerUser;

@Inject
public AsyncStorageModeValidator(
@Named("che.infra.kubernetes.pvc.strategy") String strategy,
@Named("che.infra.kubernetes.namespace.allow_user_defined")
boolean allowUserDefinedNamespaces,
@Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName,
@Named("che.limits.user.workspaces.run.count") int runtimesPerUser) {

pvcStrategy = strategy;
this.allowUserDefinedNamespaces = allowUserDefinedNamespaces;
this.defaultNamespaceName = defaultNamespaceName;
this.runtimesPerUser = runtimesPerUser;
}

@Override
public void validate(Map<String, String> attributes) throws ValidationException {
if (parseBoolean(attributes.get(ASYNC_PERSIST_ATTRIBUTE))) {
isEphemeralAttributeValidation(attributes);
pvcStrategyValidation();
alowUserDefinedNamespaceValidation();
nameSpaceStrategyValidation();
runtimesPerUserValidation();
}
}

@Override
public void validateUpdate(Map<String, String> existing, Map<String, String> update)
throws ValidationException {
if (parseBoolean(update.get(ASYNC_PERSIST_ATTRIBUTE))) {
if (isEphemeral(existing) || isEphemeral(update)) {
pvcStrategyValidation();
alowUserDefinedNamespaceValidation();
nameSpaceStrategyValidation();
runtimesPerUserValidation();
} else {
String message =
format(
"Workspace configuration not valid: Asynchronous storage available only for NOT persistent storage");
LOG.warn(message);
throw new ValidationException(message);
}
}
}

private void isEphemeralAttributeValidation(Map<String, String> attributes)
throws ValidationException {
if (!isEphemeral(attributes)) {
String message =
format(
"Workspace configuration not valid: Asynchronous storage available only for NOT persistent storage");
LOG.warn(message);
throw new ValidationException(message);
}
}

private void runtimesPerUserValidation() throws ValidationException {
if (runtimesPerUser > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be evaluated in the constructor just once.

String message =
format(
"Workspace configuration not valid: Asynchronous storage available only if 'che.limits.user.workspaces.run.count' set to 1, but got %s",
runtimesPerUser);
LOG.warn(message);
throw new ValidationException(message);
}
}

private void nameSpaceStrategyValidation() throws ValidationException {
if (!"<username>-che".equals(defaultNamespaceName)) {
String message =
format(
"Workspace configuration not valid: Asynchronous storage available only for 'per-user' namespace strategy");
LOG.warn(message);
throw new ValidationException(message);
}
}

private void alowUserDefinedNamespaceValidation() throws ValidationException {
if (allowUserDefinedNamespaces) {
String message =
format(
"Workspace configuration not valid: Asynchronous storage available only if 'che.infra.kubernetes.namespace.allow_user_defined' set to 'false', but got '%s'",
allowUserDefinedNamespaces);
LOG.warn(message);
throw new ValidationException(message);
}
}

private void pvcStrategyValidation() throws ValidationException {
if (!COMMON_STRATEGY.equals(pvcStrategy)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be evaluated in the constructor just once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String message =
format(
"Workspace configuration not valid: Asynchronous storage available only for 'common' PVC strategy, but got %s",
pvcStrategy);
LOG.warn(message);
throw new ValidationException(message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.PreviewUrlExposer;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.provision.AsyncStoragePodInterceptor;
import org.eclipse.che.workspace.infrastructure.openshift.provision.AsyncStorageProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftPreviewUrlExposer;
Expand Down Expand Up @@ -67,7 +69,9 @@ public class OpenShiftEnvironmentProvisioner
private final PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner;
private final ImagePullSecretProvisioner imagePullSecretProvisioner;
private final ProxySettingsProvisioner proxySettingsProvisioner;
private final AsyncStoragePodInterceptor asyncStoragePodObserver;
private final ServiceAccountProvisioner serviceAccountProvisioner;
private final AsyncStorageProvisioner asyncStorageProvisioner;
private final CertificateProvisioner certificateProvisioner;
private final SshKeysProvisioner sshKeysProvisioner;
private final GitConfigProvisioner gitConfigProvisioner;
Expand All @@ -88,6 +92,8 @@ public OpenShiftEnvironmentProvisioner(
PodTerminationGracePeriodProvisioner podTerminationGracePeriodProvisioner,
ImagePullSecretProvisioner imagePullSecretProvisioner,
ProxySettingsProvisioner proxySettingsProvisioner,
AsyncStorageProvisioner asyncStorageProvisioner,
AsyncStoragePodInterceptor asyncStoragePodObserver,
ServiceAccountProvisioner serviceAccountProvisioner,
CertificateProvisioner certificateProvisioner,
SshKeysProvisioner sshKeysProvisioner,
Expand All @@ -106,6 +112,8 @@ public OpenShiftEnvironmentProvisioner(
this.podTerminationGracePeriodProvisioner = podTerminationGracePeriodProvisioner;
this.imagePullSecretProvisioner = imagePullSecretProvisioner;
this.proxySettingsProvisioner = proxySettingsProvisioner;
this.asyncStorageProvisioner = asyncStorageProvisioner;
this.asyncStoragePodObserver = asyncStoragePodObserver;
this.serviceAccountProvisioner = serviceAccountProvisioner;
this.certificateProvisioner = certificateProvisioner;
this.sshKeysProvisioner = sshKeysProvisioner;
Expand All @@ -124,6 +132,7 @@ public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity)
"Start provisioning OpenShift environment for workspace '{}'", identity.getWorkspaceId());
// 1 stage - update environment according Infrastructure specific
if (pvcEnabled) {
asyncStoragePodObserver.intercept(osEnv, identity);
logsVolumeMachineProvisioner.provision(osEnv, identity);
}

Expand All @@ -144,6 +153,7 @@ public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity)
imagePullSecretProvisioner.provision(osEnv, identity);
proxySettingsProvisioner.provision(osEnv, identity);
serviceAccountProvisioner.provision(osEnv, identity);
asyncStorageProvisioner.provision(osEnv, identity);
certificateProvisioner.provision(osEnv, identity);
sshKeysProvisioner.provision(osEnv, identity);
vcsSslCertificateProvisioner.provision(osEnv, identity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProjectFactory;
import org.eclipse.che.workspace.infrastructure.openshift.project.RemoveProjectOnWorkspaceRemove;
import org.eclipse.che.workspace.infrastructure.openshift.provision.AsyncStorageProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftPreviewUrlCommandProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftCookiePathStrategy;
import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftExternalServerExposer;
Expand All @@ -93,9 +94,10 @@
public class OpenShiftInfraModule extends AbstractModule {
@Override
protected void configure() {
Multibinder.newSetBinder(binder(), WorkspaceAttributeValidator.class)
.addBinding()
.to(K8sInfraNamespaceWsAttributeValidator.class);
Multibinder<WorkspaceAttributeValidator> workspaceAttributeValidators =
Multibinder.newSetBinder(binder(), WorkspaceAttributeValidator.class);
workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class);
workspaceAttributeValidators.addBinding().to(AsyncStorageModeValidator.class);

bind(KubernetesNamespaceService.class);

Expand Down Expand Up @@ -222,5 +224,6 @@ protected void configure() {
bind(ExternalServiceExposureStrategy.class).to(OpenShiftServerExposureStrategy.class);
bind(CookiePathStrategy.class).to(OpenShiftCookiePathStrategy.class);
bind(NonTlsDistributedClusterModeNotifier.class);
bind(AsyncStorageProvisioner.class);
}
}
Loading