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

RHOAIENG-16055: new(tests): test to start a Workbench, by creating the Notebook CR directly #94

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

jiridanek
Copy link
Member

@jiridanek jiridanek commented Jan 15, 2025

Description

This is a port of

How Has This Been Tested?

  • figure out when/how to use unprivileged k8s client

Merge criteria:

  • The commits are squashed in a cohesive manner and have meaningful messages.
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work

@jiridanek jiridanek marked this pull request as draft January 15, 2025 17:04
Copy link

The following are automatically added/executed:

Available user actions:

  • To mark a PR as WIP, add /wip in a comment. To remove it from the PR comment /wip cancel to the PR.
  • To block merging of a PR, add /hold in a comment. To un-block merging of PR comment /hold cancel.
  • To mark a PR as approved, add /lgtm in a comment. To remove, add /lgtm cancel.
    lgtm label removed on each new commit push.
  • To mark PR as verified comment /verified to the PR, to un-verify comment /verified cancel to the PR.
    verified label removed on each new commit push.
Supported labels

{'/hold', '/verified', '/lgtm', '/wip'}

@jiridanek jiridanek marked this pull request as ready for review January 17, 2025 09:27
@github-actions github-actions bot added size/xl and removed size/xxl labels Jan 17, 2025
@jiridanek jiridanek force-pushed the jd_notebooks_first branch 2 times, most recently from f5edaa0 to c1eddd8 Compare January 17, 2025 09:49
@@ -0,0 +1,309 @@
from __future__ import annotations

import logging
Copy link
Collaborator

Choose a reason for hiding this comment

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

please use simple_logger

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

resource_manager.destroy()


class KubeResourceManager:
Copy link
Collaborator

Choose a reason for hiding this comment

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

please use ocpenshift python wrapper to CRUD resources

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Comment on lines 46 to 47
OPENSHIFT_DOMAIN = "openshift.io/"
ODH_DOMAIN = "opendatahub.io/"
Copy link
Collaborator

Choose a reason for hiding this comment

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

used in other places as well, please place under https://github.com/opendatahub-io/opendatahub-tests/blob/main/utilities/constants.py and re-use


LABEL_DASHBOARD = ODH_DOMAIN + "dashboard"
LABEL_ODH_MANAGED = ODH_DOMAIN + "odh-managed"
LABEL_SIDECAR_ISTIO_INJECT = "sidecar.istio.io/inject"
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

self.privileged_client = privileged_client
self.resources: list[ocp_resources.resource.Resource] = []

def createResourceWithoutWait(self, client: DynamicClient, resource: ocp_resources.resource.Resource):
Copy link
Collaborator

Choose a reason for hiding this comment

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

please use python naming conventions

Copy link
Member Author

Choose a reason for hiding this comment

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

done

import kubernetes.client
from kubernetes.dynamic import DynamicClient

from tests.workbenches.conftest import OdhAnnotationsLabels, OdhConstants, PodUtils
Copy link
Collaborator

Choose a reason for hiding this comment

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

under conftest place fixtures; functions and classes should be under utils.py in the relevant dir

@classmethod
@property
@functools.cache
def logger(cls):
Copy link
Collaborator

Choose a reason for hiding this comment

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

please use simple_logger

),
},
)
def testCreateSimpleNotebook(self, function_resource_manager, admin_client, unprivileged_client):
Copy link
Collaborator

Choose a reason for hiding this comment

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

all setup steps (e.g namespace, pvc) should be done in fixtures
please check existing fixtures and re-use

Copy link
Member Author

Choose a reason for hiding this comment

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

done

# }


def loadDefaultNotebook(client: DynamicClient, namespace: str, name: str, image: str) -> Notebook:
Copy link
Collaborator

Choose a reason for hiding this comment

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

functions should go under utils.py in the relevant dir

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,146 @@
apiVersion: kubeflow.org/v1
kind: Notebook
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there a template for this in the cluster?

Copy link
Member Author

Choose a reason for hiding this comment

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

no, we don't have template for notebooks, the only template we have is hardcoded in odh-dashboard

https://github.com/opendatahub-io/odh-dashboard/blob/eabd833b5f4b0508fe5b06235f4f670fc7e9a0db/backend/src/utils/notebookUtils.ts#L260-L280

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need the yaml? Can't we pass the arguments like any other resource?

Copy link
Member Author

Choose a reason for hiding this comment

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

i like it this way, see no reason to rewrite it


with allure.step("Create Notebook CR"):
# notebookImage: str = NotebookType.getNotebookImage(NotebookType.JUPYTER_MINIMAL_IMAGE, NotebookType.JUPYTER_MINIMAL_2023_2_TAG);
notebookImage: str = "quay.io/modh/odh-minimal-notebook-container@sha256:615af25cfd4f3f2981b173e1a5ab24cb79f268ee72dabbddb6867ee1082eb902"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this be fetched dinamically from the cluster?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, I changed the approach, thanks

Comment on lines 170 to 172
notebookString = pathlib.Path(
"/Users/jdanek/IdeaProjects/opendatahub-tests/tests/workbenches/notebook-controller/test_data/notebook.yaml"
).read_text()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Pretty sure this won't work :)

Copy link
Member Author

Choose a reason for hiding this comment

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

ofc, fixed now

Comment on lines 45 to 39
afterTestSteps={
Step(
value="Delete ODH operator and all created resources",
expected="Operator is removed and all other resources as well",
)
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this has been functionally implemented but FWIW we don't plan to remove the operator after the test run

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed the doc comment, I never tried to implement operator removal here

@@ -0,0 +1,39 @@
from typing import Callable
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this used for? does it just provide additional documentation for tests in e.g. https://github.com/opendatahub-io/opendatahub-tests/pull/94/files#diff-358f1122838e1ac22415822c5a85d419e2d111c64285e488695af1cf6f03d490R35 ?
IMHO if this is implemented we should use it across the repo to keep consistency, so this should go under a higher level directory, but it should probably be discussed beforehand. Can you attend the shift left wg meeting to do so?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure how useful this docs is going to be. Yes, it is only for structured comments. The Java version was able to render markdown docs from this, but I did not implement this in Python here.

IMO it's fine for now to only have this in the workbenches tests, and we (in workbenches team) need to decide if this is what we want to promote, or if we decide to delete this eventually.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would suggest using docstrings in general rather than trying to adapt to Java practices in this case: https://peps.python.org/pep-0257/
We haven't settled on a specific style and we're not yet rendering the documentation, but this seems to be the best bet to standardize across the repo

Copy link
Member Author

Choose a reason for hiding this comment

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

ok then

@@ -0,0 +1,309 @@
from __future__ import annotations
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the majority of these classes could be avoided if you instead used the wrapper library https://github.com/RedHatQE/openshift-python-wrapper

Copy link
Member Author

Choose a reason for hiding this comment

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

used ocp_resources more, thanks

@jiridanek jiridanek force-pushed the jd_notebooks_first branch 3 times, most recently from b956376 to bf53997 Compare February 4, 2025 08:57
@jiridanek jiridanek requested a review from rnetser February 4, 2025 08:58
@jiridanek jiridanek force-pushed the jd_notebooks_first branch 3 times, most recently from 12f966f to 21ee191 Compare February 4, 2025 09:24
@@ -0,0 +1,39 @@
from typing import Callable
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would suggest using docstrings in general rather than trying to adapt to Java practices in this case: https://peps.python.org/pep-0257/
We haven't settled on a specific style and we're not yet rendering the documentation, but this seems to be the best bet to standardize across the repo

Comment on lines 10 to 11
class Notebook(NamespacedResource):
api_group: str = "kubeflow.org"
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMHO this should be a class in ocp_resources itself

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Member Author

Choose a reason for hiding this comment

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

allright, maybe if I start contributing to ocp_resources, I can try to do the work

Copy link
Collaborator

Choose a reason for hiding this comment

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

There is a tool that will auto-generate the class for ocp_resources, https://github.com/RedHatQE/openshift-python-wrapper/blob/main/class_generator/README.md
If you need any help with it feel free to ask any of us!

Copy link
Member Author

Choose a reason for hiding this comment

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

GLOBAL_POLL_INTERVAL_MEDIUM = 10


class OcpResourceManager:
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the specific goal you have with this class? if you use resources as classes from ocp_resources you will have their lifecycle handled by the wrapper library directly

Copy link
Member Author

Choose a reason for hiding this comment

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

removed OcpResourceManager

raise RuntimeError(f"Exceptions during cleanup: {exceptions}")


class PodUtils:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above, if you use the Pod class you can already handle this without having to reimplement these checks

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -44,6 +44,7 @@ def create_ns(
teardown: bool = True,
delete_timeout: int = 4 * 60,
labels: Optional[dict[str, str]] = None,
annotations: Optional[dict[str, str]] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we want to move away from option and use the xx | yy syntax by importing from future, see https://docs.python.org/3/library/stdtypes.html#types-union

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,146 @@
apiVersion: kubeflow.org/v1
kind: Notebook
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need the yaml? Can't we pass the arguments like any other resource?

image_dict = {"jupyter-minimal-notebook": "jupyter-minimal-notebook"}
else:
image_dict = {"jupyter-minimal-notebook": "s2i-minimal-notebook"}
return registry_path + "/" + controllers_namespace + "/" + image_dict[image_name] + ":" + image_tag
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use fstring for better readability?

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Comment on lines 63 to 81
@TestDoc(
description=Desc("Create simple Notebook with all needed resources and see if Operator creates it properly"),
contact=Contact(name="Jakub Stejskal", email="[email protected]"),
steps={
Step(
value="Create namespace for Notebook resources with proper name, labels and annotations",
expected="Namespace is created",
),
Step(value="Create PVC with proper labels and data for Notebook", expected="PVC is created"),
Step(
value="Create Notebook resource with Jupyter Minimal image in pre-defined namespace",
expected="Notebook resource is created",
),
Step(
value="Wait for Notebook pods readiness",
expected="Notebook pods are up and running, Notebook is in ready state",
),
},
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this excessively verbose, but I'm open to use this format if we all agree.


nb = Notebook(yaml_file=io.StringIO(notebook_string))

return nb
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to have nb variable, just return Notebook(yaml_file=io.StringIO(notebook_string))

Copy link
Member Author

Choose a reason for hiding this comment

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

it's convenient to place breakpoint, if I ever wanted to do that, and look what's in nb

fixed now

Comment on lines 10 to 11
class Notebook(NamespacedResource):
api_group: str = "kubeflow.org"
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

name=request.param["name"],
namespace=request.param["namespace"],
label={constants.Dashboard.label: "true"},
accessmodes="ReadWriteOnce",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
accessmodes="ReadWriteOnce",
accessmodes=PersistentVolumeClaim.AccessMode.RWO,

Copy link
Member Author

Choose a reason for hiding this comment

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

done

from utilities import constants


@SuiteDoc(
Copy link
Collaborator

Choose a reason for hiding this comment

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

please see @lugi0 comment above on dosctrings

Comment on lines 45 to 61
@pytest.mark.parametrize(
"users_namespace",
[
pytest.param(
{"name": NTB_NAMESPACE},
)
],
indirect=True,
)
@pytest.mark.parametrize(
"users_persistent_volume_claim",
[
pytest.param(
{"name": NTB_NAME, "namespace": NTB_NAMESPACE},
)
],
indirect=True,
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

iiuc all resources should be created together, so please use 1 set of parameters,
e,g

    @pytest.mark.parametrize(
        "users_namespace, users_persistent_volume_claim",
        [
            pytest.param(
                {"name": NTB_NAMESPACE},
                {"name": "xxx"},
            )
        ],
        indirect=True,
    )

Copy link
Member Author

Choose a reason for hiding this comment

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

done



@pytest.fixture(scope="function")
def users_persistent_volume_claim(
Copy link
Collaborator

Choose a reason for hiding this comment

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

call users_namespace and then you can use it's attributes. e.g users_namespace.name to set the namespace

Copy link
Member Author

Choose a reason for hiding this comment

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

done



def _get_notebook_image(image_name: str, image_tag: str) -> str:
registry_path = "image-registry.openshift-image-registry.svc:5000"
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we get it from the cluster?

Copy link
Member Author

Choose a reason for hiding this comment

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

)


def _get_notebook_image(image_name: str, image_tag: str) -> str:
Copy link
Collaborator

Choose a reason for hiding this comment

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

please place functions under utils.py file to keep the tests file only for tests

Copy link
Member Author

Choose a reason for hiding this comment

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

done

notebook_string = notebook_string.replace("my-project", namespace).replace("my-workbench", name)
# Set new Route url
route_host: str = list(
Route.get(client=client, name=constants.Dashboard.route_name, namespace=py_config["applications_namespace"])
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think that client=client should be dyn_client=....

Copy link
Member Author

Choose a reason for hiding this comment

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

done

#
from __future__ import annotations

import logging
Copy link
Collaborator

Choose a reason for hiding this comment

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

please use get_logger from simple_logger

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

import kubernetes.dynamic
from kubernetes.dynamic import DynamicClient, ResourceField

import ocp_resources.pod
Copy link
Collaborator

Choose a reason for hiding this comment

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

when possible, please use explicit imports (i.e of only what is needed)

Copy link
Member Author

@jiridanek jiridanek Feb 24, 2025

Choose a reason for hiding this comment

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

from before I'm used to import packages, not individual names, as described for example in https://google.github.io/styleguide/pyguide.html#22-imports

ok, fixed

@@ -109,6 +112,19 @@ class StorageClassName:
NFS: str = "nfs"


class Dashboard:
route_name = "odh-dashboard" if py_config.get("distribution") == "upstream" else "rhods-dashboard"
label = OPENDATAHUB_IO + "/dashboard"
Copy link
Collaborator

Choose a reason for hiding this comment

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

arg name should represent what it contains; please place under labels class

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@jiridanek jiridanek requested a review from a team as a code owner February 24, 2025 10:25
from tests.workbenches.utils import step


class TestNotebookST:
Copy link
Member

Choose a reason for hiding this comment

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

what is ST?

Copy link
Member Author

@jiridanek jiridanek Feb 24, 2025

Choose a reason for hiding this comment

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

I don't rightly know, that's how it was called in https://github.com/skodjob/odh-e2e/blob/main/src/test/java/io/odh/test/e2e/standard/NotebookST.java

will remove that suffix

Copy link
Member Author

Choose a reason for hiding this comment

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

done



def _get_notebook_image(image_name: str, image_tag: str) -> str:
registry_path = "image-registry.openshift-image-registry.svc:5000"
Copy link
Member

Choose a reason for hiding this comment

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

This can be added in constant files

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@jiridanek jiridanek requested a review from rnetser February 24, 2025 12:32
),
"""
with step("Create Notebook CR"):
notebook_image: str = get_notebook_image("jupyter-minimal-notebook", "2024.2")
Copy link

Choose a reason for hiding this comment

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

notebook_image: str there is no need to declare type here when you call typed function get_notebook_image

Nit: It's better to call functions with arg=value, if for some reason get_notebook_image need to be refactored you will not be bound to positional arguments.

Copy link
Member Author

Choose a reason for hiding this comment

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


with step("Wait for Notebook pod readiness"):
with notebook:
pods = Pod.get(
Copy link

Choose a reason for hiding this comment

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

pods can be empty list and your test will pass without test anything, please assert if not pods

Copy link
Member Author

Choose a reason for hiding this comment

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

done

dyn_client=unprivileged_client, namespace=self.NTB_NAMESPACE, label_selector=f"app={self.NTB_NAME}"
)
for pod in pods:
pod.wait_for_condition(condition="Ready", status="True")
Copy link

Choose a reason for hiding this comment

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

All resources have condition and status please use them.

pod.wait_for_condition(condition=pod.Condition.READY, status=pod.Condition.Status.TRUE)

Copy link
Member Author

Choose a reason for hiding this comment

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

done



def get_notebook_image(image_name: str, image_tag: str) -> str:
controllers_namespace = py_config["applications_namespace"]
Copy link

Choose a reason for hiding this comment

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

Simple way to do this will be:

image_name = "jupyter-minimal-notebook" if py_config.get("distribution") == "upstream" else "s2i-minimal-notebook"
return f"{INTERNAL_IMAGE_REGISTRY_PATH}/{py_config["applications_namespace"]}/{image_name}:{image_tag}"

And you can drop image_name from the function args

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not bothered by the extra argument, and i think that at callsite

notebook_image: str = get_notebook_image(image_name="jupyter-minimal-notebook", image_tag="2024.2")

makes more sense than if image_name parameter was omitted.

I could (eventually) have enum for the possible image names...

Copy link

Choose a reason for hiding this comment

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

Up to you, I would remove this function and just set the path in the test where it's needed. (only used in one place) and introduce more complicity without any reason.

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 to that; used once and should be set where used

Copy link
Member Author

Choose a reason for hiding this comment

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

done

# Replace image
notebook_string = notebook_string.replace("notebook_image_placeholder", image)

return Notebook(yaml_file=io.StringIO(notebook_string))
Copy link

Choose a reason for hiding this comment

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

Resource accept kind_dict, it's better to work with dict then with string.
You can load the YAML file with yaml, change the needed values inside the dict and use it to create your resource.

Copy link
Member Author

Choose a reason for hiding this comment

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

At that point I can just as well follow other recommendations here to have the file content be a python literal in code...

I guess I'll do that.

Copy link
Member Author

Choose a reason for hiding this comment

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

went with the kind_dict, but kept using the yaml text file for the initial data

Copy link
Member Author

Choose a reason for hiding this comment

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

and kept using string replacement, because modifying the dict selectively is way more work; I need to also update values inside long strings such as container args in the pod

Copy link
Member Author

Choose a reason for hiding this comment

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

changed to using dict

notebook_string = notebook_string.replace("my-project", namespace).replace("my-workbench", name)
# Set new Route url
route_name = "odh-dashboard" if py_config.get("distribution") == "upstream" else "rhods-dashboard"
route_host: str = list(
Copy link

Choose a reason for hiding this comment

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

For better error handeling I think you should do:

route = Route(client=dyn_client, name=route_name, namespace=py_config["applications_namespace"])
if not route.exists:
     assert .......

with the current code you will get IndexError: list index out of range which say nothing.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

)
assert pods, "The expected notebook pods were not found"
for pod in pods:
pod.wait_for_condition(condition=pod.Condition.READY, status=pod.Condition.Status.TRUE)
Copy link

Choose a reason for hiding this comment

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

right now you will fail of the first pod in wait_for_condition, depends on what you want to test maybe it's better to collect the failed pods and assert once with all of them.

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks for the note; failing fast is what I want



def get_notebook_image(image_name: str, image_tag: str) -> str:
controllers_namespace = py_config["applications_namespace"]
Copy link

Choose a reason for hiding this comment

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

Up to you, I would remove this function and just set the path in the test where it's needed. (only used in one place) and introduce more complicity without any reason.



def load_default_notebook(dyn_client: DynamicClient, namespace: str, name: str, image: str) -> Notebook:
notebook_string = (pathlib.Path(__file__).parent / "notebook-controller/test_data/notebook.yaml").read_text()
Copy link

Choose a reason for hiding this comment

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

I think it's better to load the YAML here and work with dict which is python object and not with strings.

Copy link
Member Author

Choose a reason for hiding this comment

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

ack, will see if I can get approvals from rhoai people without making the change; personally I don't want to make it

Copy link
Collaborator

Choose a reason for hiding this comment

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

please work with a dict; it is more readable and pythonic

Copy link
Member Author

Choose a reason for hiding this comment

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

done



@pytest.fixture(scope="class")
def users_namespace(
Copy link
Collaborator

Choose a reason for hiding this comment

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

you can use unprivileged_model_namespace https://github.com/opendatahub-io/opendatahub-tests/blob/main/tests/model_serving/model_server/authentication/conftest.py#L334 fixture; move it to upper conftest.
you can also add to create_ns an new bool arg - add_dashboard_label and apply the logic there

Comment on lines 1 to 4
#
# Copyright Skodjob authors.
# License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
#
Copy link
Collaborator

Choose a reason for hiding this comment

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

we currently do not have license info in our files; any reason to add it?

Copy link
Member Author

Choose a reason for hiding this comment

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

saw no reason to remove it, more like; the code originally comes from odh-e2e repo

guess the world will not end in fire if I delete the header; deleted it now

Comment on lines 19 to 28
# before_test_steps
1. Step(value="Deploy Pipelines Operator", expected="Pipelines operator is available on the cluster"),
2. Step(value="Deploy ServiceMesh Operator", expected="ServiceMesh operator is available on the cluster"),
3. Step(value="Deploy Serverless Operator", expected="Serverless operator is available on the cluster"),
4. Step(value="Install ODH operator", expected="Operator is up and running and is able to serve it's operands"),
5. Step(value="Deploy DSCI", expected="DSCI is created and ready"),
6. Step(value="Deploy DSC", expected="DSC is created and ready"),

# after_test_steps
1. Step(value="Delete all created resources", expected="All created resources are removed"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is not done in the tests so i am not sure why we need to include it here.
you can call skip_if_no_deployed_openshift_serverless and skip_if_no_deployed_openshift_service_mesh to skip the tests if these are not deployed or create new fixtures to fail if these are mandaotry

Copy link
Member Author

Choose a reason for hiding this comment

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

removed

expected="Notebook pods are up and running, Notebook is in ready state",
),
"""
with step("Create Notebook CR"):
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you please explain what is the value of using with step..?

Copy link
Member Author

Choose a reason for hiding this comment

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

just fancy documentation; deleted that part

expected="Notebook pods are up and running, Notebook is in ready state",
),
"""
with step("Create Notebook CR"):
Copy link
Collaborator

Choose a reason for hiding this comment

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

each step should either be a fixture (i.e setup for a test) or a test by itself. tests should verify one thing

Copy link
Member Author

Choose a reason for hiding this comment

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

removed with step()



def get_notebook_image(image_name: str, image_tag: str) -> str:
controllers_namespace = py_config["applications_namespace"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 to that; used once and should be set where used



def load_default_notebook(dyn_client: DynamicClient, namespace: str, name: str, image: str) -> Notebook:
notebook_string = (pathlib.Path(__file__).parent / "notebook-controller/test_data/notebook.yaml").read_text()
Copy link
Collaborator

Choose a reason for hiding this comment

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

please work with a dict; it is more readable and pythonic

Comment on lines +49 to +55
"""Gets the username for the client (see kubectl -v8 auth whoami)"""
self_subject_review_resource: Resource = dyn_client.resources.get(
api_version="authentication.k8s.io/v1", kind="SelfSubjectReview"
)
self_subject_review: ResourceInstance = dyn_client.create(self_subject_review_resource)
username: str = self_subject_review.status.userInfo.username
return username
Copy link
Collaborator

Choose a reason for hiding this comment

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

wouldn'r running oc whoami with run_command be easier?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so, this way I should be able to use the same function with unpriviliged_client in some future test

Comment on lines 58 to 60
@contextmanager
def step(description: str) -> Generator[None, None, None]:
yield
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you please explain why this is needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

so that with step() that I had in the test does not throw exception that step() is not defined

I've removed the definition and all usages now

@@ -107,6 +107,7 @@ class KserveAuth:

class OpenDataHubIo:
MANAGED: str = "opendatahub.io/managed"
SERVICE_MESH: str = "opendatahub.io/service-mesh"
Copy link
Collaborator

Choose a reason for hiding this comment

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

please save opendatahub.io and re-use

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants