-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: convert bundle tests to pytest-operator (#85)
This adds a pytest-operator driven test suite for the bundle. Previously, the bundle was deployed "manually" in integrate.yaml. This refactors the test suite to deploy using pytest-operator and by substituting locally built charms into a template bundle. This commit includes some transferable helpers that should in future be migrated to a shared repo (either in pytest-operator or somewhere else like charmed-kubeflow-chisme) Also included: * Adds notes to disabled test_pipelines.py
- Loading branch information
1 parent
12cd828
commit ebcd837
Showing
8 changed files
with
421 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from _pytest.config.argparsing import Parser | ||
|
||
|
||
def pytest_addoption(parser: Parser): | ||
parser.addoption( | ||
"--bundle", | ||
default="./tests/integration/data/kfp_against_latest_edge.yaml", | ||
help="Path to bundle file to use as the template for tests. This must include all charms" | ||
"built by this bundle, where the locally built charms will replace those specified. " | ||
"This is useful for testing this bundle against different external dependencies. " | ||
"An example file is in ./tests/integration/data/kfp_against_latest_edge.yaml", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
applications: | ||
argo-controller: | ||
channel: latest/edge | ||
charm: ch:argo-controller | ||
scale: 1 | ||
istio-ingressgateway: | ||
_github_repo_name: istio-operators | ||
channel: latest/edge | ||
charm: istio-gateway | ||
options: | ||
kind: ingress | ||
scale: 1 | ||
trust: true | ||
istio-pilot: | ||
_github_repo_name: istio-operators | ||
channel: latest/edge | ||
charm: istio-pilot | ||
options: | ||
default-gateway: kubeflow-gateway | ||
scale: 1 | ||
trust: true | ||
kfp-api: | ||
channel: latest/edge | ||
charm: ch:kfp-api | ||
scale: 1 | ||
kfp-db: | ||
charm: cs:~charmed-osm/mariadb-k8s-35 | ||
options: | ||
database: mlpipeline | ||
scale: 1 | ||
kfp-persistence: | ||
channel: latest/edge | ||
charm: ch:kfp-persistence | ||
scale: 1 | ||
kfp-profile-controller: | ||
channel: latest/edge | ||
charm: ch:kfp-profile-controller | ||
scale: 1 | ||
kfp-schedwf: | ||
channel: latest/edge | ||
charm: ch:kfp-schedwf | ||
scale: 1 | ||
kfp-ui: | ||
channel: latest/edge | ||
charm: ch:kfp-ui | ||
scale: 1 | ||
kfp-viewer: | ||
channel: latest/edge | ||
charm: ch:kfp-viewer | ||
scale: 1 | ||
kfp-viz: | ||
channel: latest/edge | ||
charm: ch:kfp-viz | ||
scale: 1 | ||
kubeflow-dashboard: | ||
_github_repo_name: kubeflow-dashboard-operator | ||
channel: latest/edge | ||
charm: kubeflow-dashboard | ||
scale: 1 | ||
kubeflow-profiles: | ||
_github_repo_name: kubeflow-profiles-operator | ||
channel: latest/edge | ||
charm: kubeflow-profiles | ||
scale: 1 | ||
metacontroller-operator: | ||
channel: latest/edge | ||
charm: ch:metacontroller-operator | ||
scale: 1 | ||
trust: true | ||
minio: | ||
channel: latest/edge | ||
charm: ch:minio | ||
scale: 1 | ||
bundle: kubernetes | ||
name: kubeflow-pipelines | ||
relations: | ||
- - kfp-api | ||
- kfp-db | ||
- - kfp-api:kfp-api | ||
- kfp-persistence:kfp-api | ||
- - kfp-api:kfp-api | ||
- kfp-ui:kfp-api | ||
- - kfp-api:kfp-viz | ||
- kfp-viz:kfp-viz | ||
- - kfp-api:object-storage | ||
- minio:object-storage | ||
- - kfp-profile-controller:object-storage | ||
- minio:object-storage | ||
- - kfp-ui:object-storage | ||
- minio:object-storage | ||
- - argo-controller:object-storage | ||
- minio:object-storage | ||
- - kubeflow-profiles | ||
- kubeflow-dashboard | ||
- - istio-pilot:ingress | ||
- kubeflow-dashboard:ingress | ||
- - istio-pilot:istio-pilot | ||
- istio-ingressgateway:istio-pilot | ||
- - istio-pilot:ingress | ||
- kfp-ui:ingress |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Copyright 2022 Canonical Ltd. | ||
# See LICENSE file for licensing details. | ||
import copy | ||
from pathlib import Path | ||
from typing import Dict, Optional, Union | ||
import yaml | ||
from zipfile import ZipFile | ||
|
||
|
||
# TODO: Move this somewhere more general | ||
|
||
|
||
def get_charm_name(metadata_file: Union[Path, str]) -> str: | ||
metadata = yaml.safe_load(Path(metadata_file).read_text()) | ||
return metadata["name"] | ||
|
||
|
||
def get_charm_file(charm_dir: Path) -> Path: | ||
"""Returns the path to the .charm file representing the charm in the given directory | ||
TODO: This just assumes the suffix on the file name will be "ubuntu-20.04-amd64". | ||
Fix this in future | ||
""" | ||
charm_dir = Path(charm_dir) | ||
metadata_file = charm_dir / "metadata.yaml" | ||
charm_name = get_charm_name(metadata_file) | ||
|
||
return (charm_dir / f"{charm_name}_ubuntu-20.04-amd64.charm").absolute() | ||
|
||
|
||
def get_resources_from_charm_dir(charm_dir: Path) -> Dict[str, str]: | ||
"""Returns the resources of the charm at path""" | ||
metadata_file = charm_dir / "metadata.yaml" | ||
metadata = yaml.safe_load(Path(metadata_file).read_text()) | ||
resources = metadata["resources"] | ||
return {k: v["upstream-source"] for k, v in resources.items()} | ||
|
||
|
||
def get_resources_from_charm_file(charm_file: str) -> Dict[str, str]: | ||
"""Extracts the resources of a charm from a .charm (zipped) file.""" | ||
with ZipFile(charm_file, "r") as zip: | ||
metadata_file = zip.open("metadata.yaml") | ||
metadata = yaml.safe_load(metadata_file) | ||
resources = metadata["resources"] | ||
return {k: v["upstream-source"] for k, v in resources.items()} | ||
open_charm_file = charm_file | ||
|
||
|
||
def localize_bundle_application( | ||
bundle: dict, | ||
application: str, | ||
charm_dir: Optional[Path] = None, | ||
charm_file: Optional[Path] = None, | ||
resources: Optional[dict] = None, | ||
): | ||
"""Localize an application in a bundle, replacing its charm and resource with local files | ||
TODO: better docstring | ||
charm_file and resources can optionally be provided, otherwise they will be inferred from | ||
charm_dir. If we provide charm_file and not resources, resources will be inferred from the | ||
metadata.yaml file in the charm_file. | ||
""" | ||
bundle = copy.deepcopy(bundle) | ||
|
||
if not (charm_file or charm_dir): | ||
raise ValueError("Either charm_file or charm_dir must be provided") | ||
|
||
if charm_file: | ||
if not resources: | ||
resources = get_resources_from_charm_file(charm_file) | ||
else: | ||
charm_file = get_charm_file(charm_dir) | ||
if not resources: | ||
resources = get_resources_from_charm_dir(charm_dir) | ||
|
||
bundle["applications"][application]["charm"] = str(charm_file) | ||
bundle["applications"][application]["resources"] = resources | ||
bundle["applications"][application]["_channel"] = bundle["applications"][application][ | ||
"channel" | ||
] | ||
del bundle["applications"][application]["channel"] | ||
|
||
return bundle | ||
|
||
|
||
def main(bundle_file: str, application: str, charm_dir: str, output_file: str): | ||
bundle = yaml.safe_load(Path(bundle_file).read_text()) | ||
charm_dir = Path(charm_dir) | ||
output_bundle = localize_bundle_application(bundle, application, charm_dir) | ||
|
||
with open(output_file, "w") as fout: | ||
yaml.dump(output_bundle, fout) | ||
|
||
|
||
if __name__ == "__main__": | ||
import typer | ||
|
||
typer.run(main) |
Oops, something went wrong.