Skip to content

Commit

Permalink
Add support for Notebooks to create them in tests (#28)
Browse files Browse the repository at this point in the history
Signed-off-by: Jakub Stejskal <[email protected]>
  • Loading branch information
Frawless authored Dec 6, 2023
1 parent 87b11f3 commit 1b731f7
Show file tree
Hide file tree
Showing 21 changed files with 666 additions and 53 deletions.
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<checkstyle.version>10.12.5</checkstyle.version>
<maven.checkstyle.version>3.3.1</maven.checkstyle.version>
<maven.download.plugin.version>1.7.1</maven.download.plugin.version>
<commons.io.version>2.15.1</commons.io.version>
</properties>

<repositories>
Expand Down Expand Up @@ -161,6 +162,13 @@
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/io/odh/test/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package io.odh.test;

import io.odh.test.install.InstallTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -44,6 +45,7 @@ public class Environment {
private static final String OLM_APP_BUNDLE_PREFIX_ENV = "OLM_APP_BUNDLE_PREFIX";
private static final String OLM_OPERATOR_VERSION_ENV = "OLM_OPERATOR_VERSION";
private static final String OLM_OPERATOR_CHANNEL_ENV = "OLM_OPERATOR_CHANNEL";
private static final String OPERATOR_INSTALL_TYPE_ENV = "OPERATOR_INSTALL_TYPE";

/**
* Defaults
Expand Down Expand Up @@ -78,6 +80,9 @@ public class Environment {
public static final String OLM_APP_BUNDLE_PREFIX = getOrDefault(OLM_APP_BUNDLE_PREFIX_ENV, OLM_APP_BUNDLE_PREFIX_DEFAULT);
public static final String OLM_OPERATOR_CHANNEL = getOrDefault(OLM_OPERATOR_CHANNEL_ENV, OLM_OPERATOR_CHANNEL_DEFAULT);
public static final String OLM_OPERATOR_VERSION = getOrDefault(OLM_OPERATOR_VERSION_ENV, OLM_OPERATOR_VERSION_DEFAULT);

public static final String OPERATOR_INSTALL_TYPE = getOrDefault(OPERATOR_INSTALL_TYPE_ENV, InstallTypes.BUNDLE.toString());

private Environment() { }

static {
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/io/odh/test/OdhAnnotationsLabels.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Skodjob authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.odh.test;

public class OdhAnnotationsLabels {
public static final String OPENSHIFT_DOMAIN = "openshift.io/";
public static final String ODH_DOMAIN = "opendatahub.io/";

public static final String LABEL_DASHBOARD = ODH_DOMAIN + "dashboard";
public static final String LABEL_ODH_MANAGED = ODH_DOMAIN + "odh-managed";
public static final String LABEL_SIDECAR_ISTIO_INJECT = "sidecar.istio.io/inject";

public static final String ANNO_SERVICE_MESH = ODH_DOMAIN + "service-mesh";
public static final String ANNO_NTB_INJECT_OAUTH = "notebooks." + ODH_DOMAIN + "inject-oauth";

}
15 changes: 15 additions & 0 deletions src/main/java/io/odh/test/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
*/
package io.odh.test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.odh.test.framework.WaitException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
Expand Down Expand Up @@ -174,4 +178,15 @@ public static InputStream getFileFromResourceAsStream(String fileName) {
}

}

public static <T> T configFromYaml(String yamlFile, Class<T> c) {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
try {
return mapper.readValue(yamlFile, c);
} catch (InvalidFormatException e) {
throw new IllegalArgumentException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2 changes: 2 additions & 0 deletions src/main/java/io/odh/test/framework/TestCallbackListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.odh.test.framework;

import io.odh.test.framework.manager.ResourceManager;
import io.odh.test.platform.KubeUtils;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
Expand Down Expand Up @@ -41,6 +42,7 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception {
public void afterAll(ExtensionContext extensionContext) throws Exception {
ResourceManager.getInstance().switchToClassResourceStack();
ResourceManager.getInstance().deleteResources();
KubeUtils.clearOdhCRDs();
}

@Override
Expand Down
44 changes: 33 additions & 11 deletions src/main/java/io/odh/test/framework/manager/ResourceManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBinding;
import io.odh.test.TestConstants;
import io.odh.test.TestUtils;
import io.odh.test.framework.manager.resources.DataScienceClusterResource;
import io.odh.test.framework.manager.resources.NotebookResource;
import io.odh.test.framework.manager.resources.OperatorGroupResource;
import io.odh.test.framework.manager.resources.SubscriptionResource;
import io.odh.test.platform.KubeClient;
import io.odh.test.platform.cmdClient.KubeCmdClient;
import io.odh.test.platform.cmdClient.Oc;
import io.odh.test.utils.DeploymentUtils;
import io.opendatahub.datasciencecluster.v1.DataScienceCluster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -30,6 +35,7 @@ public class ResourceManager {

private static ResourceManager instance;
private static KubeClient client;
private static KubeCmdClient kubeCmdClient;

static final Stack<ResourceItem> CLASS_RESOURCE_STACK = new Stack<>();
static final Stack<ResourceItem> METHOD_RESOURCE_STACK = new Stack<>();
Expand All @@ -40,6 +46,7 @@ public static synchronized ResourceManager getInstance() {
if (instance == null) {
instance = new ResourceManager();
client = new KubeClient(TestConstants.DEFAULT_NAMESPACE);
kubeCmdClient = new Oc();
}
return instance;
}
Expand All @@ -48,9 +55,15 @@ public static KubeClient getClient() {
return client;
}

public static KubeCmdClient getKubeCmdClient() {
return kubeCmdClient;
}

private final ResourceType<?>[] resourceTypes = new ResourceType[]{
new SubscriptionResource(),
new OperatorGroupResource(),
new DataScienceClusterResource(),
new NotebookResource(),
};

public final void switchToTestResourceStack() {
Expand Down Expand Up @@ -80,6 +93,14 @@ private <T extends HasMetadata> void createResource(boolean waitReady, T... reso
for (T resource : resources) {
ResourceType<T> type = findResourceType(resource);

synchronized (this) {
resourceStackPointer.push(
new ResourceItem<T>(
() -> deleteResource(resource),
resource
));
}

if (resource.getMetadata().getNamespace() == null) {
LOGGER.info("Creating/Updating {} {}",
resource.getKind(), resource.getMetadata().getName());
Expand All @@ -91,12 +112,21 @@ private <T extends HasMetadata> void createResource(boolean waitReady, T... reso
if (type == null) {
if (resource instanceof Deployment) {
Deployment deployment = (Deployment) resource;
client.getClient().apps().deployments().resource(deployment).create();
if (client.getClient().apps().deployments().resource(deployment).get() != null) {
client.getClient().apps().deployments().resource(deployment).update();
} else {
client.getClient().apps().deployments().resource(deployment).create();
}
if (waitReady) {
DeploymentUtils.waitForDeploymentReady(resource.getMetadata().getNamespace(), resource.getMetadata().getName());
}
} else {
client.getClient().resource(resource).create();
if (client.getClient().resource(resource).get() != null) {
client.getClient().resource(resource).update();
} else {
client.getClient().resource(resource).create();
}

}
} else {
type.create(resource);
Expand All @@ -105,14 +135,6 @@ private <T extends HasMetadata> void createResource(boolean waitReady, T... reso
String.format("Timed out waiting for %s %s/%s to be ready", resource.getKind(), resource.getMetadata().getNamespace(), resource.getMetadata().getName()));
}
}

synchronized (this) {
resourceStackPointer.push(
new ResourceItem<T>(
() -> deleteResource(resource),
resource
));
}
}
}

Expand Down Expand Up @@ -172,7 +194,7 @@ public final <T extends HasMetadata> boolean waitResourceCondition(T resource, R
assertNotNull(resource.getMetadata().getName());

// cluster role binding and custom resource definition does not need namespace...
if (!(resource instanceof ClusterRoleBinding || resource instanceof CustomResourceDefinition || resource instanceof ClusterRole || resource instanceof ValidatingWebhookConfiguration)) {
if (!(resource instanceof ClusterRoleBinding || resource instanceof CustomResourceDefinition || resource instanceof ClusterRole || resource instanceof ValidatingWebhookConfiguration || resource instanceof DataScienceCluster)) {
assertNotNull(resource.getMetadata().getNamespace());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright Skodjob authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.odh.test.framework.manager.resources;

import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.odh.test.TestConstants;
import io.odh.test.TestUtils;
import io.odh.test.framework.manager.ResourceManager;
import io.odh.test.framework.manager.ResourceType;
import io.odh.test.platform.KubeUtils;
import io.opendatahub.datasciencecluster.v1.DataScienceCluster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DataScienceClusterResource implements ResourceType<DataScienceCluster> {

private static final Logger LOGGER = LoggerFactory.getLogger(DataScienceClusterResource.class);
@Override
public String getKind() {
return "DataScienceCluster";
}

@Override
public DataScienceCluster get(String namespace, String name) {
return dataScienceCLusterClient().withName(name).get();
}

@Override
public void create(DataScienceCluster resource) {
dataScienceCLusterClient().resource(resource).create();
}

@Override
public void delete(DataScienceCluster resource) {
dataScienceCLusterClient().withName(resource.getMetadata().getName()).delete();
}

@Override
public void update(DataScienceCluster resource) {
dataScienceCLusterClient().resource(resource).update();
}

@Override
public boolean waitForReadiness(DataScienceCluster resource) {
String message = String.format("DataScienceCluster %s readiness", resource.getMetadata().getName());
TestUtils.waitFor(message, TestConstants.GLOBAL_POLL_INTERVAL_SHORT, TestConstants.GLOBAL_TIMEOUT, () -> {
boolean dscReady;

DataScienceCluster dsc = dataScienceCLusterClient().withName(resource.getMetadata().getName()).get();

String dashboardStatus = KubeUtils.getDscConditionByType(dsc.getStatus().getConditions(), "dashboardReady").getStatus();
LOGGER.debug("DataScienceCluster {} dashboard status: {}", resource.getMetadata().getName(), dashboardStatus);
dscReady = dashboardStatus.equals("True");

String workbenchesStatus = KubeUtils.getDscConditionByType(dsc.getStatus().getConditions(), "workbenchesReady").getStatus();
LOGGER.debug("DataScienceCluster {} workbenches status: {}", resource.getMetadata().getName(), workbenchesStatus);
dscReady = dscReady && workbenchesStatus.equals("True");

return dscReady;
}, () -> { });
return true;
}

public static MixedOperation<DataScienceCluster, KubernetesResourceList<DataScienceCluster>, Resource<DataScienceCluster>> dataScienceCLusterClient() {
return ResourceManager.getClient().getClient().resources(DataScienceCluster.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Skodjob authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.odh.test.framework.manager.resources;

import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.odh.test.TestUtils;
import io.odh.test.framework.manager.ResourceManager;
import io.odh.test.framework.manager.ResourceType;
import org.kubeflow.v1.Notebook;

import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;


public class NotebookResource implements ResourceType<Notebook> {

private static final String NOTEBOOK_TEMPLATE_PATH = "notebook.yaml";
@Override
public String getKind() {
return "Notebook";
}

@Override
public Notebook get(String namespace, String name) {
return notebookClient().inNamespace(namespace).withName(name).get();
}

@Override
public void create(Notebook resource) {
notebookClient().inNamespace(resource.getMetadata().getNamespace()).resource(resource).create();
}

@Override
public void delete(Notebook resource) {
notebookClient().inNamespace(resource.getMetadata().getNamespace()).withName(resource.getMetadata().getName()).delete();
}

@Override
public void update(Notebook resource) {
notebookClient().inNamespace(resource.getMetadata().getNamespace()).resource(resource).update();
}

@Override
public boolean waitForReadiness(Notebook resource) {
return resource != null;
}

public static MixedOperation<Notebook, KubernetesResourceList<Notebook>, Resource<Notebook>> notebookClient() {
return ResourceManager.getClient().getClient().resources(Notebook.class);
}

public static Notebook loadDefaultNotebook(String namespace, String name) throws IOException {
InputStream is = TestUtils.getFileFromResourceAsStream(NOTEBOOK_TEMPLATE_PATH);
String notebookString = IOUtils.toString(is, "UTF-8");
notebookString = notebookString.replace("my-project", namespace).replace("my-workbench", name);
return TestUtils.configFromYaml(notebookString, Notebook.class);
}
}
5 changes: 4 additions & 1 deletion src/main/java/io/odh/test/install/BundleInstall.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import io.odh.test.Environment;
import io.odh.test.TestConstants;
import io.odh.test.TestUtils;
import io.odh.test.framework.manager.ResourceItem;
import io.odh.test.framework.manager.ResourceManager;
import io.odh.test.platform.KubeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -55,7 +57,8 @@ public void printResources() {
});
}

public void installBundle() {
public void create() {
ResourceManager.getInstance().createResourceWithWait(resources.toArray(new HasMetadata[0]));
ResourceManager.getInstance().pushToStack(new ResourceItem(KubeUtils::deleteDefaultDSCI, null));
}
}
10 changes: 10 additions & 0 deletions src/main/java/io/odh/test/install/InstallTypes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Skodjob authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.odh.test.install;

public enum InstallTypes {
OLM,
BUNDLE
}
Loading

0 comments on commit 1b731f7

Please sign in to comment.