-
Notifications
You must be signed in to change notification settings - Fork 23
[CLOUDP-352109] Run MCK E2E tests against OCI published helm chart #509
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
base: master
Are you sure you want to change the base?
Changes from 25 commits
dfbc534
982c77d
0daaf61
4c753fc
8b511e5
4f50441
087e06a
cb2a8d2
b1162ec
8a50020
c04e43a
4132fc6
82bf02e
6e641ce
e96e180
93f3e12
eb65be5
5cb41a0
8f50012
1068518
829393f
3aa3fce
ca7881c
084b8f2
e1700ca
2cc7bbd
f53071f
c482251
1ee164a
ffceb0c
cfee047
1c8f00e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| OCI_HELM_VERSION = "OCI_HELM_VERSION" | ||
| OCI_HELM_REGISTRY_ENV_VAR_NAME = "OCI_HELM_REGISTRY" | ||
| OCI_HELM_REPOSITORY_ENV_VAR_NAME = "OCI_HELM_REPOSITORY" | ||
| OCI_HELM_REGION_ENV_VAR_NAME = "OCI_HELM_REGION" | ||
|
|
||
| LEGACY_OPERATOR_CHART = "mongodb/enterprise-operator" | ||
| MCK_HELM_CHART = "mongodb/mongodb-kubernetes" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,10 +6,15 @@ | |
| import uuid | ||
| from typing import Dict, List, Optional, Tuple | ||
|
|
||
| from kubetester.consts import * | ||
| from tests import test_logger | ||
|
|
||
| logger = test_logger.get_test_logger(__name__) | ||
|
|
||
| # LOCAL_CRDs_DIR is the dir where local helm chart's CRDs are copied in tests image | ||
| LOCAL_CRDs_DIR = "helm_chart/crds" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Mixed casing |
||
| OCI_HELM_REGISTRY_ECR = "268558157000.dkr.ecr.us-east-1.amazonaws.com" | ||
|
|
||
|
|
||
| def helm_template( | ||
| helm_args: Dict, | ||
|
|
@@ -145,6 +150,55 @@ def process_run_and_check(args, **kwargs): | |
| raise | ||
|
|
||
|
|
||
| def helm_registry_login_to_ecr(helm_registry: str, region: str): | ||
| logger.info(f"Attempting to log into ECR registry: {helm_registry}, using helm registry login.") | ||
|
|
||
| aws_command = ["aws", "ecr", "get-login-password", "--region", region] | ||
|
|
||
| # as we can see the password is being provided by stdin, that would mean we will have to | ||
| # pipe the aws_command (it figures out the password) into helm_command. | ||
| helm_command = ["helm", "registry", "login", "--username", "AWS", "--password-stdin", helm_registry] | ||
|
|
||
| try: | ||
| logger.info("Starting AWS ECR credential retrieval.") | ||
| aws_proc = subprocess.Popen( | ||
| aws_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True # Treat input/output as text strings | ||
| ) | ||
|
|
||
| logger.info("Starting Helm registry login.") | ||
| helm_proc = subprocess.Popen( | ||
| helm_command, stdin=aws_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True | ||
MaciejKaras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) | ||
|
|
||
| # Close the stdout stream of aws_proc in the parent process | ||
| # to prevent resource leakage (only needed if you plan to do more processing) | ||
| aws_proc.stdout.close() | ||
|
|
||
| # Wait for the Helm command (helm_proc) to finish and capture its output | ||
| helm_stdout, helm_stderr = helm_proc.communicate() | ||
|
|
||
| # Wait for the AWS process to finish as well | ||
| aws_proc.wait() | ||
|
|
||
| if aws_proc.returncode != 0: | ||
| _, aws_stderr = aws_proc.communicate() | ||
| raise Exception(f"aws command to get password failed. Error: {aws_stderr}") | ||
|
|
||
| if helm_proc.returncode == 0: | ||
| logger.info("Login to helm registry was successful.") | ||
| logger.info(helm_stdout.strip()) | ||
| else: | ||
| raise Exception( | ||
| f"Login to helm registry failed, Exit code: {helm_proc.returncode}, Error: {helm_stderr.strip()}" | ||
| ) | ||
|
|
||
| except FileNotFoundError as e: | ||
| # This catches errors if 'aws' or 'helm' are not in the PATH | ||
| raise Exception(f"Command not found. Please ensure '{e.filename}' is installed and in your system's PATH.") | ||
| except Exception as e: | ||
| raise Exception(f"An unexpected error occurred: {e}.") | ||
|
|
||
|
|
||
| def helm_upgrade( | ||
| name: str, | ||
| namespace: str, | ||
|
|
@@ -162,7 +216,7 @@ def helm_upgrade( | |
| chart_dir = helm_chart_path if helm_override_path else _helm_chart_dir(helm_chart_path) | ||
|
|
||
| if apply_crds_first: | ||
| apply_crds_from_chart(chart_dir) | ||
| apply_crds_from_chart(LOCAL_CRDs_DIR) | ||
|
|
||
| command_args = _create_helm_args(helm_args, helm_options) | ||
| args = [ | ||
|
|
@@ -183,11 +237,24 @@ def helm_upgrade( | |
| process_run_and_check(command, check=True, capture_output=True, shell=True) | ||
|
|
||
|
|
||
| def apply_crds_from_chart(chart_dir: str): | ||
| crd_files = glob.glob(os.path.join(chart_dir, "crds", "*.yaml")) | ||
| # oci_chart_info returns the respective registry/repo and region information | ||
| # based on the build scenario (dev/staging) tests are being run in. These are | ||
| # read from build_info.json and then set to the tests image as env vars. | ||
| def oci_chart_info(): | ||
| registry = os.environ.get(OCI_HELM_REGISTRY_ENV_VAR_NAME) | ||
| repository = os.environ.get(OCI_HELM_REPOSITORY_ENV_VAR_NAME) | ||
| region = os.environ.get(OCI_HELM_REGION_ENV_VAR_NAME) | ||
|
|
||
| logger.info(f"oci chart details in test image is registry {registry}, repo {repository}, region {region}") | ||
|
|
||
| return registry, f"{repository}/mongodb-kubernetes", region | ||
|
|
||
|
|
||
| def apply_crds_from_chart(crds_dir: str): | ||
| crd_files = glob.glob(os.path.join(crds_dir, "*.yaml")) | ||
|
|
||
| if not crd_files: | ||
| raise Exception(f"No CRD files found in chart directory: {chart_dir}") | ||
| raise Exception(f"No CRD files found in chart directory: {crds_dir}") | ||
|
|
||
| logger.info(f"Found {len(crd_files)} CRD files to apply:") | ||
|
|
||
|
|
@@ -234,3 +301,41 @@ def _create_helm_args(helm_args: Dict[str, str], helm_options: Optional[List[str | |
|
|
||
| def _helm_chart_dir(default: Optional[str] = "helm_chart") -> str: | ||
| return os.environ.get("HELM_CHART_DIR", default) | ||
|
|
||
|
|
||
| # helm_chart_path_and_version returns the chart path and version that we would like to install to run the E2E tests. | ||
| # for local tests it returns early with local helm chart dir and for other scenarios it figures out the chart and version | ||
| # based on the caller. In most of the cases we will install chart from OCI registry but for the tests where we would like | ||
| # to install MEKO's specific version or MCK's specific version, we would expect `helm_chart_path` to set already. | ||
| def helm_chart_path_and_version(helm_chart_path: str, operator_version: str) -> tuple[str, str]: | ||
| # these are imported here to resolve import cycle issue | ||
| from tests.conftest import LOCAL_HELM_CHART_DIR, local_operator | ||
|
|
||
| if local_operator(): | ||
| return LOCAL_HELM_CHART_DIR, "" | ||
|
|
||
| # if operator_version is not specified, and we are not installing the MCK or MEKO chart | ||
| # it would mean we want to install OCI published helm chart. | ||
| if not operator_version and helm_chart_path not in ( | ||
| MCK_HELM_CHART, | ||
| LEGACY_OPERATOR_CHART, | ||
| ): | ||
| non_semver_operator_version = os.environ.get(OCI_HELM_VERSION) | ||
| operator_version = f"0.0.0+{non_semver_operator_version}" | ||
|
|
||
| # helm_chart_path not being passed would mean we are on evg env and would like to | ||
| # install helm chart from OCI registry. | ||
| if not helm_chart_path: | ||
| registry, repository, region = oci_chart_info() | ||
| # If ECR we need to login first to the OCI container registry | ||
| if registry == OCI_HELM_REGISTRY_ECR: | ||
| try: | ||
| helm_registry_login_to_ecr(registry, region) | ||
| except Exception as e: | ||
| raise Exception(f"Failed to login to ECR helm registry {registry}. Error: {e}") | ||
|
|
||
| # figure out the registry URI, based on dev/staging scenario | ||
| chart_uri = f"oci://{registry}/{repository}" | ||
| helm_chart_path = chart_uri | ||
|
|
||
| return helm_chart_path, operator_version | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,5 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| import time | ||
| from typing import Dict, List, Optional | ||
|
|
||
|
|
@@ -11,6 +10,7 @@ | |
| from kubetester import wait_for_webhook | ||
| from kubetester.create_or_replace_from_yaml import create_or_replace_from_yaml | ||
| from kubetester.helm import ( | ||
| helm_chart_path_and_version, | ||
| helm_install, | ||
| helm_repo_add, | ||
| helm_template, | ||
|
|
@@ -25,6 +25,7 @@ | |
| "opsmanagers.mongodb.com", | ||
| ) | ||
|
|
||
|
|
||
| logger = test_logger.get_test_logger(__name__) | ||
|
|
||
|
|
||
|
|
@@ -43,14 +44,16 @@ def __init__( | |
| namespace: str, | ||
| helm_args: Optional[Dict] = None, | ||
| helm_options: Optional[List[str]] = None, | ||
| helm_chart_path: Optional[str] = "helm_chart", | ||
| helm_chart_path: Optional[str] = None, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: how can we instruct this function to use local helm charts for local tests? Ideally we should be able to set this somewhere in the context files and override it by default locally, for example by specifying it in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's the plan. I am working on the change that will make sure that we are able to run the tests locally. Will push the changes soon. |
||
| name: Optional[str] = "mongodb-kubernetes-operator", | ||
| api_client: Optional[client.api_client.ApiClient] = None, | ||
| operator_version: Optional[str] = None, | ||
| ): | ||
|
|
||
| # The Operator will be installed from the following repo, so adding it first | ||
| helm_repo_add("mongodb", "https://mongodb.github.io/helm-charts") | ||
|
|
||
| helm_chart_path, operator_version = helm_chart_path_and_version(helm_chart_path, operator_version) | ||
|
|
||
| if helm_args is None: | ||
| helm_args = {} | ||
|
|
||
|
|
@@ -69,6 +72,7 @@ def __init__( | |
| self.helm_chart_path = helm_chart_path | ||
| self.name = name | ||
| self.api_client = api_client | ||
| self.operator_version = operator_version | ||
|
|
||
| def install_from_template(self): | ||
| """Uses helm to generate yaml specification and then uses python K8s client to apply them to the cluster | ||
|
|
@@ -82,6 +86,9 @@ def install_from_template(self): | |
|
|
||
| def install(self, custom_operator_version: Optional[str] = None) -> Operator: | ||
| """Installs the Operator to Kubernetes cluster using 'helm install', waits until it's running""" | ||
| if not custom_operator_version: | ||
| custom_operator_version = self.operator_version | ||
|
|
||
| helm_install( | ||
| self.name, | ||
| self.namespace, | ||
|
|
@@ -99,6 +106,9 @@ def upgrade( | |
| self, multi_cluster: bool = False, custom_operator_version: Optional[str] = None, apply_crds_first: bool = False | ||
| ) -> Operator: | ||
| """Upgrades the Operator in Kubernetes cluster using 'helm upgrade', waits until it's running""" | ||
| if not custom_operator_version: | ||
| custom_operator_version = self.operator_version | ||
|
|
||
| helm_upgrade( | ||
| self.name, | ||
| self.namespace, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Prefer explicit import for code navigation.