Skip to content

Commit

Permalink
#200: Initial crash loop prevention for custom resources (#216)
Browse files Browse the repository at this point in the history
NOTE: Status handling for app definitions is commented out as there are still some issues

- Catch all exceptions when adding session, workspace, appdefinition resources and add error state
- Extend the resource clients for app definitions, sessions, and
workspaces to be able to handle the status subresource.
- Add basic status for our CRDs to track handling state of the operator
- Prevent crashloops by never handling resources that are in error or
handling state. The latter indicates an unexcepted crash during handling
(e.g. NPE).
- NOTE: The previous step was only dony for LAZY handlers. EAGER handlers did not change
- Exemplary sub steps for volume claim an attachment for workspace resources
- Increase resource versions for all CRs
- Move API, KIND, and CRD_NAME constants from Spec to resource classes
(e.g. from SessionSpec to Session) because they belong to the resource
itself. This became apparent when adding the Status to the resources

Part of #200
  • Loading branch information
lucas-koehler authored Aug 2, 2023
1 parent e0397b6 commit 92ed4b9
Show file tree
Hide file tree
Showing 28 changed files with 533 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 EclipseSource and others.
* Copyright (C) 2022-2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -18,7 +18,8 @@
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinition;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionSpec;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionSpecResourceList;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionStatus;

public interface AppDefinitionResourceClient
extends CustomResourceClient<AppDefinitionSpec, AppDefinition, AppDefinitionSpecResourceList> {
public interface AppDefinitionResourceClient extends
CustomResourceClient<AppDefinitionSpec, AppDefinitionStatus, AppDefinition, AppDefinitionSpecResourceList> {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 EclipseSource and others.
* Copyright (C) 2022-2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -18,32 +18,49 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.eclipse.theia.cloud.common.k8s.resource.UserScopedSpec;

import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.client.CustomResource;

public interface CustomResourceClient<S, T extends CustomResource<S, Void>, L extends KubernetesResourceList<T>>
public interface CustomResourceClient<SPEC, STATUS, T extends CustomResource<SPEC, STATUS>, L extends KubernetesResourceList<T>>
extends ResourceClient<T, L> {

T create(String correlationId, S spec);
T create(String correlationId, SPEC spec);

default Optional<S> spec(String name) {
default Optional<SPEC> spec(String name) {
return get(name).map(T::getSpec);
}

default Optional<STATUS> status(String name) {
return get(name).map(T::getStatus);
}

default List<T> list(String user) {
return list().stream().filter(item -> Objects.equals(UserScopedSpec.getUser(item.getSpec()), user))
.collect(Collectors.toList());
}

default List<S> specs() {
default List<SPEC> specs() {
return list().stream().map(item -> item.getSpec()).collect(Collectors.toList());
}

default List<S> specs(String user) {
default List<SPEC> specs(String user) {
return list(user).stream().map(item -> item.getSpec()).collect(Collectors.toList());
}

default boolean updateStatus(String correlationId, T resource, Consumer<STATUS> editOperation) {
trace(correlationId, "Update Status of " + resource);
final String name = resource.getMetadata().getName();
return (editStatus(correlationId, name, res -> {
STATUS status = Optional.ofNullable(res.getStatus()).orElse(createDefaultStatus());
res.setStatus(status);
editOperation.accept(status);
}) != null);
}

STATUS createDefaultStatus();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinition;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionSpec;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionSpecResourceList;
import org.eclipse.theia.cloud.common.k8s.resource.AppDefinitionStatus;

import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.NamespacedKubernetesClient;
Expand All @@ -42,4 +43,9 @@ public AppDefinition create(String correlationId, AppDefinitionSpec spec) {
return operation().create(appDefinition);
}

@Override
public AppDefinitionStatus createDefaultStatus() {
return new AppDefinitionStatus();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.theia.cloud.common.k8s.resource.Session;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpec;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpecResourceList;
import org.eclipse.theia.cloud.common.k8s.resource.SessionStatus;
import org.eclipse.theia.cloud.common.util.TheiaCloudError;

import io.fabric8.kubernetes.api.model.ObjectMeta;
Expand Down Expand Up @@ -102,4 +103,9 @@ public boolean reportActivity(String correlationId, String name) {
}) != null;
}

@Override
public SessionStatus createDefaultStatus() {
return new SessionStatus();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 EclipseSource and others.
* Copyright (C) 2022-2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -20,6 +20,7 @@
import org.eclipse.theia.cloud.common.k8s.resource.Workspace;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceSpec;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceSpecResourceList;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceStatus;
import org.eclipse.theia.cloud.common.util.TheiaCloudError;

import io.fabric8.kubernetes.api.model.ObjectMeta;
Expand Down Expand Up @@ -88,4 +89,9 @@ protected boolean isWorkspaceComplete(String correlationId, WorkspaceSpec create
}
return false;
}

@Override
public WorkspaceStatus createDefaultStatus() {
return new WorkspaceStatus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ default T edit(String correlationId, String name, Consumer<T> consumer) {
return resource.edit(JavaUtil.toUnary(consumer));
}

default T editStatus(String correlationId, String name, Consumer<T> consumer) {
trace(correlationId, "Edit status of " + name);
Resource<T> resource = resource(name);
if (resource.get() == null) {
return null;
}
return resource.editStatus(JavaUtil.toUnary(consumer));
}

Optional<T> loadAndCreate(String correlationId, String yaml, Consumer<T> customization);

default Optional<T> loadAndCreate(String correlationId, String yaml) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 EclipseSource and others.
* Copyright (C) 2022-2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -20,8 +20,10 @@
import org.eclipse.theia.cloud.common.k8s.resource.Session;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpec;
import org.eclipse.theia.cloud.common.k8s.resource.SessionSpecResourceList;
import org.eclipse.theia.cloud.common.k8s.resource.SessionStatus;

public interface SessionResourceClient extends CustomResourceClient<SessionSpec, Session, SessionSpecResourceList> {
public interface SessionResourceClient
extends CustomResourceClient<SessionSpec, SessionStatus, Session, SessionSpecResourceList> {
Session launch(String correlationId, SessionSpec spec, long timeout, TimeUnit unit);

default Session launch(String correlationId, SessionSpec spec, int timeout) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 EclipseSource and others.
* Copyright (C) 2022-2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -20,14 +20,14 @@
import org.eclipse.theia.cloud.common.k8s.resource.Workspace;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceSpec;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceSpecResourceList;
import org.eclipse.theia.cloud.common.k8s.resource.WorkspaceStatus;

public interface WorkspaceResourceClient
extends CustomResourceClient<WorkspaceSpec, Workspace, WorkspaceSpecResourceList> {
extends CustomResourceClient<WorkspaceSpec, WorkspaceStatus, Workspace, WorkspaceSpecResourceList> {

Workspace launch(String correlationId, WorkspaceSpec spec, long timeout, TimeUnit unit);

default Workspace launch(String correlationId, WorkspaceSpec spec) {
return launch(correlationId, spec, 1, TimeUnit.MINUTES);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@
import io.fabric8.kubernetes.model.annotation.Singular;
import io.fabric8.kubernetes.model.annotation.Version;

@Version("v6beta")
@Version("v7beta")
@Group("theia.cloud")
@Singular("appdefinition")
@Plural("appdefinitions")
public class AppDefinition extends CustomResource<AppDefinitionSpec, Void> implements Namespaced {
public class AppDefinition extends CustomResource<AppDefinitionSpec, AppDefinitionStatus> implements Namespaced {

private static final long serialVersionUID = 8749670583218521755L;
public static final String API = "theia.cloud/v7beta";
public static final String KIND = "AppDefinition";
public static final String CRD_NAME = "appdefinitions.theia.cloud";

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@
@JsonDeserialize()
public class AppDefinitionSpec {

public static final String API = "theia.cloud/v6beta";
public static final String KIND = "AppDefinition";
public static final String CRD_NAME = "appdefinitions.theia.cloud";

@JsonProperty("name")
private String name;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/********************************************************************************
* Copyright (C) 2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
package org.eclipse.theia.cloud.common.k8s.resource;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonDeserialize
public class AppDefinitionStatus extends ResourceStatus {
// This class is empty as only the common properties of the super class are
// used. Already define a specific class to allow easier extension, properly
// type the resources and resource clients.
// It is planned to extend this later with AppDefinition specific status steps.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/********************************************************************************
* Copyright (C) 2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
package org.eclipse.theia.cloud.common.k8s.resource;

/**
* Constant values to describe resource handling.
*/
public interface OperatorStatus {

/**
* The default status describing that the resource is new and was not handled
* before.
*/
String NEW = "NEW";

/** The operator tried to handle this resource but an error occurred. */
String ERROR = "ERROR";

/** The operator started handling this resource. */
String HANDLING = "HANDLING";

/** The operator successfully finished handling this resource. */
String HANDLED = "HANDLED";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/********************************************************************************
* Copyright (C) 2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
package org.eclipse.theia.cloud.common.k8s.resource;

import com.fasterxml.jackson.annotation.JsonProperty;

public abstract class ResourceStatus {

@JsonProperty()
private String operatorStatus;

@JsonProperty()
private String operatorMessage;

public String getOperatorStatus() {
return operatorStatus;
}

public void setOperatorStatus(String operatorStatus) {
this.operatorStatus = operatorStatus;
}

public String getOperatorMessage() {
return operatorMessage;
}

public void setOperatorMessage(String operatorMessage) {
this.operatorMessage = operatorMessage;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 EclipseSource, Lockular, Ericsson, STMicroelectronics and
* Copyright (C) 2022-2023 EclipseSource, Lockular, Ericsson, STMicroelectronics and
* others.
*
* This program and the accompanying materials are made available under the
Expand All @@ -25,13 +25,16 @@
import io.fabric8.kubernetes.model.annotation.Singular;
import io.fabric8.kubernetes.model.annotation.Version;

@Version("v4beta")
@Version("v5beta")
@Group("theia.cloud")
@Singular("session")
@Plural("sessions")
public class Session extends CustomResource<SessionSpec, Void> implements Namespaced {
public class Session extends CustomResource<SessionSpec, SessionStatus> implements Namespaced {

private static final long serialVersionUID = 4518092300237069237L;
public static final String API = "theia.cloud/v5beta";
public static final String KIND = "Session";
public static final String CRD_NAME = "sessions.theia.cloud";

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@
@JsonDeserialize()
public class SessionSpec implements UserScopedSpec {

public static final String API = "theia.cloud/v4beta";
public static final String KIND = "Session";
public static final String CRD_NAME = "sessions.theia.cloud";

@JsonProperty("name")
private String name;

Expand Down
Loading

0 comments on commit 92ed4b9

Please sign in to comment.