Skip to content

Commit 21ee191

Browse files
committed
RHOAIENG-16055: new(tests): test to start a Workbench by creating the Notebook CR directly
1 parent f87b5bc commit 21ee191

9 files changed

Lines changed: 628 additions & 0 deletions

File tree

tests/workbenches/__init__.py

Whitespace-only changes.

tests/workbenches/conftest.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#
2+
# Copyright Skodjob authors.
3+
# License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
#
5+
from __future__ import annotations
6+
7+
from typing import Generator
8+
9+
from kubernetes.dynamic import DynamicClient
10+
11+
from ocp_resources.namespace import Namespace
12+
from ocp_resources.persistent_volume_claim import PersistentVolumeClaim
13+
14+
import pytest
15+
16+
from utilities import constants
17+
from utilities.infra import create_ns
18+
19+
20+
@pytest.fixture(scope="class")
21+
def users_namespace(
22+
request: pytest.FixtureRequest, unprivileged_client: DynamicClient
23+
) -> Generator[Namespace, None, None]:
24+
with create_ns(
25+
unprivileged_client=unprivileged_client,
26+
name=request.param["name"],
27+
labels={constants.Dashboard.label: "true"},
28+
annotations={constants.ServiceMesh.annotation_inject: "false"},
29+
) as ns:
30+
yield ns
31+
32+
33+
@pytest.fixture(scope="function")
34+
def users_persistent_volume_claim(
35+
request: pytest.FixtureRequest, unprivileged_client: DynamicClient
36+
) -> Generator[PersistentVolumeClaim, None, None]:
37+
with PersistentVolumeClaim(
38+
client=unprivileged_client,
39+
name=request.param["name"],
40+
namespace=request.param["namespace"],
41+
label={constants.Dashboard.label: "true"},
42+
accessmodes="ReadWriteOnce",
43+
size="10Gi",
44+
volume_mode=PersistentVolumeClaim.VolumeMode.FILE,
45+
) as pvc:
46+
yield pvc

tests/workbenches/docs.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#
2+
# Copyright Skodjob authors.
3+
# License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
#
5+
from __future__ import annotations
6+
7+
from typing import Callable
8+
import typing
9+
10+
T = typing.TypeVar("T") # noqa: FCN001
11+
StepRType = tuple[str, str]
12+
13+
14+
def Desc(value: str) -> str:
15+
return value
16+
17+
18+
def Step(
19+
value: str,
20+
expected: str,
21+
) -> StepRType:
22+
return value, expected
23+
24+
25+
def SuiteDoc(
26+
description: str,
27+
before_test_steps: set[StepRType],
28+
after_test_steps: set[StepRType],
29+
) -> Callable[[T], T]:
30+
return lambda x: x
31+
32+
33+
def Contact(
34+
name: str,
35+
email: str,
36+
) -> tuple[str, str]:
37+
return name, email
38+
39+
40+
def TestDoc(
41+
description: str,
42+
contact: tuple[str, str],
43+
steps: set[StepRType],
44+
) -> Callable[[T], T]:
45+
return lambda x: x
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
apiVersion: kubeflow.org/v1
2+
kind: Notebook
3+
metadata:
4+
annotations:
5+
notebooks.opendatahub.io/inject-oauth: 'true'
6+
opendatahub.io/service-mesh: 'false'
7+
opendatahub.io/accelerator-name: ''
8+
labels:
9+
app: my-workbench
10+
opendatahub.io/dashboard: 'true'
11+
opendatahub.io/odh-managed: 'true'
12+
sidecar.istio.io/inject: 'false'
13+
name: my-workbench
14+
namespace: my-project
15+
spec:
16+
template:
17+
spec:
18+
affinity: {}
19+
containers:
20+
- env:
21+
- name: NOTEBOOK_ARGS
22+
value: |-
23+
--ServerApp.port=8888
24+
--ServerApp.token=''
25+
--ServerApp.password=''
26+
--ServerApp.base_url=/notebook/my-project/my-workbench
27+
--ServerApp.quit_button=False
28+
--ServerApp.tornado_settings={"user":"odh_user","hub_host":"odh_dashboard_route","hub_prefix":"/projects/my-project"}
29+
- name: JUPYTER_IMAGE
30+
value: notebook_image_placeholder
31+
image: notebook_image_placeholder
32+
imagePullPolicy: Always
33+
livenessProbe:
34+
failureThreshold: 3
35+
httpGet:
36+
path: /notebook/my-project/my-workbench/api
37+
port: notebook-port
38+
scheme: HTTP
39+
initialDelaySeconds: 10
40+
periodSeconds: 5
41+
successThreshold: 1
42+
timeoutSeconds: 1
43+
name: my-workbench
44+
ports:
45+
- containerPort: 8888
46+
name: notebook-port
47+
protocol: TCP
48+
readinessProbe:
49+
failureThreshold: 3
50+
httpGet:
51+
path: /notebook/my-project/my-workbench/api
52+
port: notebook-port
53+
scheme: HTTP
54+
initialDelaySeconds: 10
55+
periodSeconds: 5
56+
successThreshold: 1
57+
timeoutSeconds: 1
58+
resources:
59+
limits:
60+
cpu: "2"
61+
memory: 4Gi
62+
requests:
63+
cpu: "1"
64+
memory: 1Gi
65+
volumeMounts:
66+
- mountPath: /opt/app-root/src
67+
name: my-workbench
68+
- mountPath: /dev/shm
69+
name: shm
70+
workingDir: /opt/app-root/src
71+
- args:
72+
- --provider=openshift
73+
- --https-address=:8443
74+
- --http-address=
75+
- --openshift-service-account=my-workbench
76+
- --cookie-secret-file=/etc/oauth/config/cookie_secret
77+
- --cookie-expire=24h0m0s
78+
- --tls-cert=/etc/tls/private/tls.crt
79+
- --tls-key=/etc/tls/private/tls.key
80+
- --upstream=http://localhost:8888
81+
- --upstream-ca=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
82+
- --email-domain=*
83+
- --skip-provider-button
84+
- --openshift-sar={"verb":"get","resource":"notebooks","resourceAPIGroup":"kubeflow.org","resourceName":"my-workbench","namespace":"$(NAMESPACE)"}
85+
- --logout-url=odh_dashboard_route/projects/my-project?notebookLogout=my-workbench
86+
env:
87+
- name: NAMESPACE
88+
valueFrom:
89+
fieldRef:
90+
fieldPath: metadata.namespace
91+
image: registry.redhat.io/openshift4/ose-oauth-proxy:v4.10
92+
imagePullPolicy: Always
93+
livenessProbe:
94+
failureThreshold: 3
95+
httpGet:
96+
path: /oauth/healthz
97+
port: oauth-proxy
98+
scheme: HTTPS
99+
initialDelaySeconds: 30
100+
periodSeconds: 5
101+
successThreshold: 1
102+
timeoutSeconds: 1
103+
name: oauth-proxy
104+
ports:
105+
- containerPort: 8443
106+
name: oauth-proxy
107+
protocol: TCP
108+
readinessProbe:
109+
failureThreshold: 3
110+
httpGet:
111+
path: /oauth/healthz
112+
port: oauth-proxy
113+
scheme: HTTPS
114+
initialDelaySeconds: 5
115+
periodSeconds: 5
116+
successThreshold: 1
117+
timeoutSeconds: 1
118+
resources:
119+
limits:
120+
cpu: 100m
121+
memory: 64Mi
122+
requests:
123+
cpu: 100m
124+
memory: 64Mi
125+
volumeMounts:
126+
- mountPath: /etc/oauth/config
127+
name: oauth-config
128+
- mountPath: /etc/tls/private
129+
name: tls-certificates
130+
enableServiceLinks: false
131+
serviceAccountName: my-workbench
132+
volumes:
133+
- name: my-workbench
134+
persistentVolumeClaim:
135+
claimName: my-workbench
136+
- emptyDir:
137+
medium: Memory
138+
name: shm
139+
- name: oauth-config
140+
secret:
141+
defaultMode: 420
142+
secretName: my-workbench-oauth-config # pragma: allowlist secret
143+
- name: tls-certificates
144+
secret:
145+
defaultMode: 420
146+
secretName: my-workbench-tls # pragma: allowlist secret
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#
2+
# Copyright Skodjob authors.
3+
# License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
#
5+
from __future__ import annotations
6+
7+
import io
8+
import pathlib
9+
10+
import kubernetes.client
11+
from kubernetes.dynamic import DynamicClient
12+
13+
from ocp_resources.route import Route
14+
15+
import pytest
16+
from pytest_testconfig import config as py_config
17+
18+
from tests.workbenches.docs import TestDoc, SuiteDoc, Contact, Desc, Step
19+
from tests.workbenches.resources import Notebook
20+
from tests.workbenches.utils import step, PodUtils, OcpResourceManager
21+
from utilities import constants
22+
23+
24+
@SuiteDoc(
25+
description=Desc("Verifies deployments of Notebooks via GitOps approach"),
26+
before_test_steps={
27+
Step(value="Deploy Pipelines Operator", expected="Pipelines operator is available on the cluster"),
28+
Step(value="Deploy ServiceMesh Operator", expected="ServiceMesh operator is available on the cluster"),
29+
Step(value="Deploy Serverless Operator", expected="Serverless operator is available on the cluster"),
30+
Step(value="Install ODH operator", expected="Operator is up and running and is able to serve it's operands"),
31+
Step(value="Deploy DSCI", expected="DSCI is created and ready"),
32+
Step(value="Deploy DSC", expected="DSC is created and ready"),
33+
},
34+
after_test_steps={
35+
Step(
36+
value="Delete all created resources",
37+
expected="All created resources are removed",
38+
)
39+
},
40+
)
41+
class TestNotebookST:
42+
NTB_NAME: str = "test-odh-notebook"
43+
NTB_NAMESPACE: str = "test-odh-notebook"
44+
45+
@pytest.mark.parametrize(
46+
"users_namespace",
47+
[
48+
pytest.param(
49+
{"name": NTB_NAMESPACE},
50+
)
51+
],
52+
indirect=True,
53+
)
54+
@pytest.mark.parametrize(
55+
"users_persistent_volume_claim",
56+
[
57+
pytest.param(
58+
{"name": NTB_NAME, "namespace": NTB_NAMESPACE},
59+
)
60+
],
61+
indirect=True,
62+
)
63+
@TestDoc(
64+
description=Desc("Create simple Notebook with all needed resources and see if Operator creates it properly"),
65+
contact=Contact(name="Jakub Stejskal", email="jstejska@redhat.com"),
66+
steps={
67+
Step(
68+
value="Create namespace for Notebook resources with proper name, labels and annotations",
69+
expected="Namespace is created",
70+
),
71+
Step(value="Create PVC with proper labels and data for Notebook", expected="PVC is created"),
72+
Step(
73+
value="Create Notebook resource with Jupyter Minimal image in pre-defined namespace",
74+
expected="Notebook resource is created",
75+
),
76+
Step(
77+
value="Wait for Notebook pods readiness",
78+
expected="Notebook pods are up and running, Notebook is in ready state",
79+
),
80+
},
81+
)
82+
def test_create_simple_notebook(
83+
self, admin_client, unprivileged_client, users_namespace, users_persistent_volume_claim
84+
):
85+
with OcpResourceManager() as test_frame:
86+
with step("Create Notebook CR"):
87+
notebook_image: str = _get_notebook_image("jupyter-minimal-notebook", "2024.2")
88+
notebook = _load_default_notebook(
89+
client=unprivileged_client, namespace=self.NTB_NAMESPACE, name=self.NTB_NAME, image=notebook_image
90+
)
91+
test_frame.enter(resource=notebook)
92+
93+
with step("Wait for Notebook pod readiness"):
94+
PodUtils.wait_for_pods_ready(
95+
client=admin_client,
96+
namespace_name=self.NTB_NAMESPACE,
97+
label_selector=f"app={self.NTB_NAME}",
98+
expect_pods_count=1,
99+
)
100+
101+
102+
def _get_notebook_image(image_name: str, image_tag: str) -> str:
103+
registry_path = "image-registry.openshift-image-registry.svc:5000"
104+
controllers_namespace = py_config["applications_namespace"]
105+
if py_config.get("distribution") == "upstream":
106+
image_dict = {"jupyter-minimal-notebook": "jupyter-minimal-notebook"}
107+
else:
108+
image_dict = {"jupyter-minimal-notebook": "s2i-minimal-notebook"}
109+
return registry_path + "/" + controllers_namespace + "/" + image_dict[image_name] + ":" + image_tag
110+
111+
112+
def _load_default_notebook(client: DynamicClient, namespace: str, name: str, image: str) -> Notebook:
113+
notebook_string = (pathlib.Path(__file__).parent / "test_data/notebook.yaml").read_text()
114+
notebook_string = notebook_string.replace("my-project", namespace).replace("my-workbench", name)
115+
# Set new Route url
116+
route_host: str = list(
117+
Route.get(client=client, name=constants.Dashboard.route_name, namespace=py_config["applications_namespace"])
118+
)[0].host
119+
notebook_string = notebook_string.replace("odh_dashboard_route", "https://" + route_host)
120+
# Set the correct username
121+
username = _get_username(client=client)
122+
notebook_string = notebook_string.replace("odh_user", username)
123+
# Replace image
124+
notebook_string = notebook_string.replace("notebook_image_placeholder", image)
125+
126+
nb = Notebook(yaml_file=io.StringIO(notebook_string))
127+
128+
return nb
129+
130+
131+
def _get_username(client: DynamicClient) -> str:
132+
"""Gets the username for the client (see kubectl -v8 auth whoami)"""
133+
self_subject_review_resource: kubernetes.dynamic.Resource = client.resources.get(
134+
api_version="authentication.k8s.io/v1", kind="SelfSubjectReview"
135+
)
136+
self_subject_review: kubernetes.dynamic.ResourceInstance = client.create(self_subject_review_resource)
137+
username: str = self_subject_review.status.userInfo.username
138+
return username

tests/workbenches/resources.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#
2+
# Copyright Skodjob authors.
3+
# License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
#
5+
from __future__ import annotations
6+
7+
from ocp_resources.resource import NamespacedResource
8+
9+
10+
class Notebook(NamespacedResource):
11+
api_group: str = "kubeflow.org"

0 commit comments

Comments
 (0)