From f1f52addc8e91e97d82f334e314cf7fb245ff340 Mon Sep 17 00:00:00 2001 From: Divya Madala <113469545+Divyaasm@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:20:20 -0800 Subject: [PATCH] Support new distribution-validations and add few enhancements to validation workflow (#4447) Signed-off-by: Divya Madala --- src/validation_workflow/deb/__init__.py | 8 + src/validation_workflow/deb/validation_deb.py | 61 +++++++ .../docker/inspect_docker_image.py | 2 +- .../docker/validation_docker.py | 51 +----- src/validation_workflow/rpm/validation_rpm.py | 37 +--- src/validation_workflow/tar/validation_tar.py | 38 +---- src/validation_workflow/validation.py | 70 +++++++- src/validation_workflow/validation_args.py | 40 +++-- .../validation_test_runner.py | 6 +- src/validation_workflow/yum/validation_yum.py | 39 +---- src/validation_workflow/zip/__init__.py | 8 + src/validation_workflow/zip/validation_zip.py | 65 +++++++ .../test_inspect_docker_image.py | 3 +- .../test_validation.py | 68 +++++++- .../test_validation_args.py | 47 ++++- .../test_validation_deb.py | 160 ++++++++++++++++++ .../test_validation_docker.py | 105 +++++++----- .../test_validation_rpm.py | 48 ++++-- .../test_validation_tar.py | 44 +++-- .../test_validation_yum.py | 43 +++-- .../test_validation_zip.py | 146 ++++++++++++++++ 21 files changed, 850 insertions(+), 239 deletions(-) create mode 100644 src/validation_workflow/deb/__init__.py create mode 100644 src/validation_workflow/deb/validation_deb.py create mode 100644 src/validation_workflow/zip/__init__.py create mode 100644 src/validation_workflow/zip/validation_zip.py create mode 100644 tests/tests_validation_workflow/test_validation_deb.py create mode 100644 tests/tests_validation_workflow/test_validation_zip.py diff --git a/src/validation_workflow/deb/__init__.py b/src/validation_workflow/deb/__init__.py new file mode 100644 index 0000000000..153f30145e --- /dev/null +++ b/src/validation_workflow/deb/__init__.py @@ -0,0 +1,8 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# +# This page intentionally left blank. diff --git a/src/validation_workflow/deb/validation_deb.py b/src/validation_workflow/deb/validation_deb.py new file mode 100644 index 0000000000..82b3f7763c --- /dev/null +++ b/src/validation_workflow/deb/validation_deb.py @@ -0,0 +1,61 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. + +import logging +import os + +from system.execute import execute +from test_workflow.integ_test.utils import get_password +from validation_workflow.api_test_cases import ApiTestCases +from validation_workflow.download_utils import DownloadUtils +from validation_workflow.validation import Validation +from validation_workflow.validation_args import ValidationArgs + + +class ValidateDeb(Validation, DownloadUtils): + def __init__(self, args: ValidationArgs) -> None: + super().__init__(args) + + def installation(self) -> bool: + try: + for project in self.args.projects: + set_password = f' env OPENSEARCH_INITIAL_ADMIN_PASSWORD={get_password(str(self.args.version))}' if project == "opensearch" else "" + execute(f'sudo dpkg --purge {project}', ".") + execute(f'sudo {set_password} dpkg -i {os.path.basename(self.args.file_path.get(project))}', str(self.tmp_dir.path)) + except: + raise Exception("Failed to install OpenSearch/OpenSearch-Dashboards") + return True + + def start_cluster(self) -> bool: + try: + for project in self.args.projects: + execute(f'sudo systemctl enable {project}', ".") + execute(f'sudo systemctl start {project}', ".") + execute(f'sudo systemctl status {project}', ".") + except: + raise Exception('Failed to Start Cluster') + return True + + def validation(self) -> bool: + if self.check_cluster_readiness(): + test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, + self.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if self.args.allow_http else True) + if (test_result): + logging.info(f'All tests Pass : {counter}') + return True + else: + raise Exception(f'Not all tests Pass : {counter}') + else: + raise Exception("Cluster is not ready for API test") + + def cleanup(self) -> bool: + try: + for project in self.args.projects: + execute(f'sudo dpkg --purge {project}', ".") + except Exception as e: + raise Exception(f'Exception occurred either while attempting to stop cluster or removing OpenSearch/OpenSearch-Dashboards. {str(e)}') + return True diff --git a/src/validation_workflow/docker/inspect_docker_image.py b/src/validation_workflow/docker/inspect_docker_image.py index 3b311006a4..40b3c9c177 100644 --- a/src/validation_workflow/docker/inspect_docker_image.py +++ b/src/validation_workflow/docker/inspect_docker_image.py @@ -27,7 +27,7 @@ def __init__(self, image_id: str, image_name: str, prod_image_tag: str) -> None: self.image_id = image_id self.image_name = image_name self.prod_image_tag = prod_image_tag - self.image_tag = ValidationArgs().stg_tag('opensearch_dashboards').replace(" ", "") if ("dashboards" in self.image_name) else ValidationArgs().stg_tag('opensearch').replace(" ", "") + self.image_tag = ValidationArgs().stg_tag('opensearch-dashboards').replace(" ", "") if ("dashboards" in self.image_name) else ValidationArgs().stg_tag('opensearch').replace(" ", "") self.auth_token_url = "https://auth.docker.io/token?" self.auth_service_scope = "service=registry.docker.io&scope=repository:" self.registry_url = "https://index.docker.io/v2/" diff --git a/src/validation_workflow/docker/validation_docker.py b/src/validation_workflow/docker/validation_docker.py index bc2d7f976f..54b0716427 100644 --- a/src/validation_workflow/docker/validation_docker.py +++ b/src/validation_workflow/docker/validation_docker.py @@ -9,14 +9,10 @@ import os import shutil import subprocess -import time from subprocess import PIPE from typing import Any -import requests - -from system.temporary_directory import TemporaryDirectory -from validation_workflow.api_request import ApiTest +from test_workflow.integ_test.utils import get_password from validation_workflow.api_test_cases import ApiTestCases from validation_workflow.docker.inspect_docker_image import InspectDockerImage from validation_workflow.validation import Validation @@ -61,8 +57,7 @@ def start_cluster(self) -> bool: def validation(self) -> bool: # STEP 2 . inspect image digest between opensearchproject(downloaded/local) and opensearchstaging(dockerHub) if not self.args.using_staging_artifact_only: - self.image_names_list = [self.args.OS_image, self.args.OSD_image] - self.image_names_list = [x for x in self.image_names_list if (os.path.basename(x) in self.args.projects)] + self.image_names_list = ['opensearchproject/' + project for project in self.args.projects] self.image_digests = list(map(lambda x: self.inspect_docker_image(x[0], x[1]), zip(self.image_ids.values(), self.image_names_list))) # type: ignore if all(self.image_digests): logging.info('Image digest is validated.\n\n') @@ -79,7 +74,7 @@ def validation(self) -> bool: self.args.version ) if return_code: - logging.info('Checking if cluster is ready for API test in every 10 seconds\n\n') + logging.info('Checking if cluster is ready for API test in every 5 seconds\n\n') if self.check_cluster_readiness(): # STEP 4 . OS, OSD API validation @@ -94,8 +89,8 @@ def validation(self) -> bool: raise Exception(f'Not all tests Pass : {_counter}') else: raise Exception("Cluster is not ready for API test.") - else: - raise Exception('The container failed to start. Exiting the validation.') + else: + raise Exception('The container failed to start. Exiting the validation.') return True @@ -129,36 +124,6 @@ def cleanup_process(self) -> bool: return('returncode=0' in (str(result))) - def check_http_request(self) -> bool: - self.test_readiness_urls = { - 'https://localhost:9200/': 'opensearch cluster API', - 'http://localhost:5601/api/status': 'opensearch-dashboards API', - } - - for url, name in self.test_readiness_urls.items(): - try: - status_code, response_text = ApiTest(url, self.args.version).api_get() - if status_code != 200: - logging.error(f'Error connecting to {name} ({url}): status code {status_code}') - return False - except (requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout) as e: - logging.error(f'Error connecting to {name} ({url}): {e}') - return False - return True - - def check_cluster_readiness(self) -> bool: - max_retry = 20 - retry_count = 0 - while retry_count < max_retry: - logging.info(f'sleeping 10sec for retry {retry_count + 1}/{max_retry}') - time.sleep(10) - if self.check_http_request(): - logging.info('\n\ncluster is now ready for API test\n\n') - return True - retry_count += 1 - logging.error(f"Maximum number of retries ({max_retry}) reached. Cluster is not ready for API test.") - return False - def get_artifact_image_name(self, artifact: str, using_staging_artifact_only: str) -> Any: self.image_names = { 'dockerhub': { @@ -241,7 +206,6 @@ def run_container(self, image_ids: dict, version: str) -> Any: '2': 'docker-compose-2.x.yml' } - self.tmp_dir = TemporaryDirectory() self.target_yml_file = os.path.join(self.tmp_dir.name, 'docker-compose.yml') self.major_version_number = version[0] @@ -252,8 +216,9 @@ def run_container(self, image_ids: dict, version: str) -> Any: self.replacements = [(f'opensearchproject/{key}:{self.major_version_number}', value) for key, value in image_ids.items()] list(map(lambda r: self.inplace_change(self.target_yml_file, r[0], r[1]), self.replacements)) - + os.environ["OPENSEARCH_INITIAL_ADMIN_PASSWORD"] = get_password(str(version)) # spin up containers - self.docker_compose_up = f'docker-compose -f {self.target_yml_file} up -d' + services = "opensearch-node1 opensearch-node2" if "opensearch-dashboards" not in self.args.projects else "" + self.docker_compose_up = f'docker-compose -f {self.target_yml_file} up -d {services}' result = subprocess.run(self.docker_compose_up, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True) return ('returncode=0' in (str(result)), self.target_yml_file) diff --git a/src/validation_workflow/rpm/validation_rpm.py b/src/validation_workflow/rpm/validation_rpm.py index 9d1f6aa7b8..991f99fd63 100644 --- a/src/validation_workflow/rpm/validation_rpm.py +++ b/src/validation_workflow/rpm/validation_rpm.py @@ -7,10 +7,8 @@ import logging import os -import time from system.execute import execute -from system.temporary_directory import TemporaryDirectory from test_workflow.integ_test.utils import get_password from validation_workflow.api_test_cases import ApiTestCases from validation_workflow.download_utils import DownloadUtils @@ -22,26 +20,6 @@ class ValidateRpm(Validation, DownloadUtils): def __init__(self, args: ValidationArgs) -> None: super().__init__(args) - self.base_url_production = "https://artifacts.opensearch.org/releases/bundle/" - self.base_url_staging = "https://ci.opensearch.org/ci/dbc/distribution-build-" - self.tmp_dir = TemporaryDirectory() - - def download_artifacts(self) -> bool: - isFilePathEmpty = bool(self.args.file_path) - for project in self.args.projects: - if (isFilePathEmpty): - if ("https:" not in self.args.file_path.get(project)): - self.copy_artifact(self.args.file_path.get(project), str(self.tmp_dir.path)) - else: - self.args.version = self.get_version(self.args.file_path.get(project)) - self.check_url(self.args.file_path.get(project)) - else: - if (self.args.artifact_type == "staging"): - self.args.file_path[project] = f"{self.base_url_staging}{project}/{self.args.version}/{self.args.build_number[project]}/linux/{self.args.arch}/{self.args.distribution}/dist/{project}/{project}-{self.args.version}-linux-{self.args.arch}.rpm" # noqa: E501 - else: - self.args.file_path[project] = f"{self.base_url_production}{project}/{self.args.version}/{project}-{self.args.version}-linux-{self.args.arch}.rpm" - self.check_url(self.args.file_path.get(project)) - return True def installation(self) -> bool: try: @@ -58,7 +36,6 @@ def start_cluster(self) -> bool: try: for project in self.args.projects: execute(f'sudo systemctl start {project}', ".") - time.sleep(20) (stdout, stderr, status) = execute(f'sudo systemctl status {project}', ".") if(status == 0): logging.info(stdout) @@ -70,12 +47,16 @@ def start_cluster(self) -> bool: return True def validation(self) -> bool: - test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, self.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if not self.args.force_https else True) # noqa: E501 - if (test_result): - logging.info(f'All tests Pass : {counter}') - return True + if self.check_cluster_readiness(): + test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, + self.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if self.args.allow_http else True) + if (test_result): + logging.info(f'All tests Pass : {counter}') + return True + else: + raise Exception(f'Not all tests Pass : {counter}') else: - raise Exception(f'Not all tests Pass : {counter}') + raise Exception("Cluster is not ready for API test") def cleanup(self) -> bool: try: diff --git a/src/validation_workflow/tar/validation_tar.py b/src/validation_workflow/tar/validation_tar.py index f136590f6c..60f8c24343 100644 --- a/src/validation_workflow/tar/validation_tar.py +++ b/src/validation_workflow/tar/validation_tar.py @@ -7,11 +7,9 @@ import logging import os -import time from system.execute import execute from system.process import Process -from system.temporary_directory import TemporaryDirectory from test_workflow.integ_test.utils import get_password from validation_workflow.api_test_cases import ApiTestCases from validation_workflow.download_utils import DownloadUtils @@ -23,29 +21,9 @@ class ValidateTar(Validation, DownloadUtils): def __init__(self, args: ValidationArgs) -> None: super().__init__(args) - self.base_url_production = "https://artifacts.opensearch.org/releases/bundle/" - self.base_url_staging = "https://ci.opensearch.org/ci/dbc/distribution-build-" - self.tmp_dir = TemporaryDirectory() self.os_process = Process() self.osd_process = Process() - def download_artifacts(self) -> bool: - isFilePathEmpty = bool(self.args.file_path) - for project in self.args.projects: - if (isFilePathEmpty): - if ("https:" not in self.args.file_path.get(project)): - self.copy_artifact(self.args.file_path.get(project), str(self.tmp_dir.path)) - else: - self.args.version = self.get_version(self.args.file_path.get(project)) - self.check_url(self.args.file_path.get(project)) - else: - if (self.args.artifact_type == "staging"): - self.args.file_path[project] = f"{self.base_url_staging}{project}/{self.args.version}/{self.args.build_number[project]}/linux/{self.args.arch}/{self.args.distribution}/dist/{project}/{project}-{self.args.version}-linux-{self.args.arch}.tar.gz" # noqa: E501 - else: - self.args.file_path[project] = f"{self.base_url_production}{project}/{self.args.version}/{project}-{self.args.version}-linux-{self.args.arch}.tar.gz" - self.check_url(self.args.file_path.get(project)) - return True - def installation(self) -> bool: try: for project in self.args.projects: @@ -58,22 +36,24 @@ def installation(self) -> bool: def start_cluster(self) -> bool: try: self.os_process.start(f'export OPENSEARCH_INITIAL_ADMIN_PASSWORD={get_password(str(self.args.version))} && ./opensearch-tar-install.sh', os.path.join(self.tmp_dir.path, "opensearch")) - time.sleep(85) if ("opensearch-dashboards" in self.args.projects): self.osd_process.start(os.path.join(str(self.tmp_dir.path), "opensearch-dashboards", "bin", "opensearch-dashboards"), ".") - time.sleep(20) logging.info('Started cluster') except: raise Exception('Failed to Start Cluster') return True def validation(self) -> bool: - test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, self.check_for_security_plugin(os.path.join(self.tmp_dir.path, "opensearch")) if not self.args.force_https else True) # noqa: E501 - if (test_result): - logging.info(f'All tests Pass : {counter}') + if self.check_cluster_readiness(): + test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, + self.check_for_security_plugin(os.path.join(self.tmp_dir.path, "opensearch")) if self.args.allow_http else True) + if (test_result): + logging.info(f'All tests Pass : {counter}') + return True + else: + raise Exception(f'Not all tests Pass : {counter}') else: - raise Exception(f'Not all tests Pass : {counter}') - return True + raise Exception("Cluster is not ready for API test") def cleanup(self) -> bool: try: diff --git a/src/validation_workflow/validation.py b/src/validation_workflow/validation.py index 92520491e0..095a085eba 100644 --- a/src/validation_workflow/validation.py +++ b/src/validation_workflow/validation.py @@ -10,9 +10,14 @@ import os import re import shutil +import time from abc import ABC, abstractmethod from typing import Any +import requests + +from system.temporary_directory import TemporaryDirectory +from validation_workflow.api_request import ApiTest from validation_workflow.download_utils import DownloadUtils from validation_workflow.validation_args import ValidationArgs @@ -23,8 +28,10 @@ class Validation(ABC): """ def __init__(self, args: ValidationArgs) -> None: - super().__init__() self.args = args + self.base_url_production = "https://artifacts.opensearch.org/releases/bundle/" + self.base_url_staging = "https://ci.opensearch.org/ci/dbc/distribution-build-" + self.tmp_dir = TemporaryDirectory() def check_url(self, url: str) -> bool: if DownloadUtils().download(url, self.tmp_dir) and DownloadUtils().is_url_valid(url): # type: ignore @@ -53,9 +60,66 @@ def run(self) -> Any: except Exception as e: raise Exception(f'An error occurred while running the validation tests: {str(e)}') - @abstractmethod def download_artifacts(self) -> bool: - pass + isFilePathEmpty = bool(self.args.file_path) + for project in self.args.projects: + if (isFilePathEmpty): + if ("https:" not in self.args.file_path.get(project)): + self.copy_artifact(self.args.file_path.get(project), str(self.tmp_dir.path)) + else: + self.args.version = self.get_version(self.args.file_path.get(project)) + self.check_url(self.args.file_path.get(project)) + else: + self.args.file_path[project] = self.get_filepath(project) + self.check_url(self.args.file_path.get(project)) + return True + + def get_filepath(self, project: str) -> str: + file_name_suffix = "tar.gz" if self.args.distribution == "tar" else self.args.distribution + if self.args.artifact_type == "staging": + if self.args.distribution == "yum": + return ( + f"{self.base_url_staging}{project}/{self.args.version}/{self.args.build_number[project]}/{self.args.platform}/" + f"{self.args.arch}/rpm/dist/{project}/{project}-{self.args.version}.staging.repo" + ) + return ( + f"{self.base_url_staging}{project}/{self.args.version}/{self.args.build_number[project]}/{self.args.platform}/" + f"{self.args.arch}/{self.args.distribution}/dist/{project}/{project}-{self.args.version}-{self.args.platform}-" + f"{self.args.arch}.{file_name_suffix}" + ) + if self.args.distribution == "yum": + return f"{self.base_url_production}{project}/{self.args.version[0:1]}.x/{project}-{self.args.version[0:1]}.x.repo" + return f"{self.base_url_production}{project}/{self.args.version}/{project}-{self.args.version}-{self.args.platform}-{self.args.arch}.{file_name_suffix}" + + def check_cluster_readiness(self) -> bool: + max_retry = 20 + retry_count = 0 + while retry_count < max_retry: + logging.info(f'Sleeping 5sec for retry {retry_count + 1}/{max_retry}') + time.sleep(5) + if self.check_http_request(): + logging.info('\n\nCluster is now ready for API test\n\n') + return True + retry_count += 1 + logging.error(f"Maximum number of retries ({max_retry}) reached. Cluster is not ready for API test.") + return False + + def check_http_request(self) -> bool: + self.test_readiness_urls = { + 'https://localhost:9200': 'opensearch cluster API' + } + if 'opensearch-dashboards' in self.args.projects: + self.test_readiness_urls['http://localhost:5601/api/status'] = 'opensearch-dashboards API' + for url, name in self.test_readiness_urls.items(): + try: + status_code, response_text = ApiTest(url, self.args.version).api_get() + if status_code != 200: + logging.error(f'Error connecting to {name} ({url}): status code {status_code}') + return False + except (requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout) as e: + logging.error(f'Error connecting to {name} ({url}): {e}') + return False + return True @abstractmethod def installation(self) -> bool: diff --git a/src/validation_workflow/validation_args.py b/src/validation_workflow/validation_args.py index 44af126081..511f6ca691 100644 --- a/src/validation_workflow/validation_args.py +++ b/src/validation_workflow/validation_args.py @@ -13,7 +13,7 @@ class ValidationArgs: - SUPPORTED_PLATFORMS = ["linux"] + SUPPORTED_PLATFORMS = ["linux", "windows"] DOCKER_SOURCE = ["dockerhub", "ecr"] def __init__(self) -> None: @@ -113,44 +113,52 @@ def __init__(self) -> None: ) parser.add_argument( "-f", - "--force-https", + "--allow-http", action="store_true", - default=True, - help="If False, use http/https to connect based on the existence of security plugin, else always use https, default to True" + default=False, + help="If True, use http/https to connect based on the existence of security plugin, else always use https, default to False" ) group = parser.add_mutually_exclusive_group() group.add_argument( "--validate-digest-only", action="store_true", default=False, - help="(optional) Validate digest only; will not run docker to test API") + help="(optional) Validate digest only; will not run docker to test API", + dest="validate_digest_only" + ) group.add_argument( "--using-staging-artifact-only", action="store_true", default=False, - help="(optional) Use only staging artifact to run docker and API test, will not validate digest") + help="(optional) Use only staging artifact to run docker and API test, will not validate digest", + dest="using_staging_artifact_only" + ) args = parser.parse_args() if (not (args.version or args.file_path)): raise Exception("Provide either version number or File Path") - if(args.file_path): + if (args.file_path): + if 'opensearch' not in args.file_path.keys() or not set(args.file_path.keys()) <= {'opensearch', 'opensearch-dashboards'}: + raise Exception("Missing OpenSearch artifact details! Please provide the valid product names among opensearch and opensearch-dashboards") args.distribution = self.get_distribution_type(args.file_path) args.projects = args.file_path.keys() - if (('opensearch' not in args.projects)): - raise Exception("Missing OpenSearch OpenSearch artifact details! Please provide the same along with OpenSearch-Dashboards to validate") + + if args.distribution == "docker": + if args.osd_build_number != "latest" and "opensearch-dashboards" not in args.projects: + raise Exception("--osd_build_number argument cannot be provided without specifying opensearch-dashboards in --projects") + if not(args.validate_digest_only) and not(args.using_staging_artifact_only): + raise Exception("Provide either of --validate-digest-only and --using-staging-artifact-only for Docker Validation") self.version = args.version self.file_path = args.file_path self.artifact_type = args.artifact_type - self.force_https = args.force_https + self.allow_http = args.allow_http self.logging_level = args.logging_level self.distribution = args.distribution self.platform = args.platform self.projects = args.projects self.arch = args.arch - self.OS_image = 'opensearchproject/opensearch' - self.OSD_image = 'opensearchproject/opensearch-dashboards' self.build_number = {"opensearch": args.os_build_number, "opensearch-dashboards": args.osd_build_number} self.os_build_number = args.os_build_number self.osd_build_number = args.osd_build_number @@ -165,6 +173,10 @@ def get_distribution_type(self, file_path: dict) -> str: return 'yum' elif (any("rpm" in value for value in file_path.values())): return 'rpm' + elif (any("zip" in value for value in file_path.values())): + return 'zip' + elif (any("deb" in value for value in file_path.values())): + return 'deb' else: raise Exception("Provided distribution is not supported") @@ -174,8 +186,8 @@ def stg_tag(self, image_type: str) -> str: None, [ self.version, - "." + self.os_build_number if (self.os_build_number != "") and (image_type == "opensearch") else None, - "." + self.osd_build_number if (self.osd_build_number != "") and (image_type == "opensearch_dashboards") else None, + "." + self.os_build_number if (self.os_build_number != "latest") and (image_type == "opensearch") else None, + "." + self.osd_build_number if (self.osd_build_number != "latest") and (image_type == "opensearch-dashboards") else None, ], ) ) diff --git a/src/validation_workflow/validation_test_runner.py b/src/validation_workflow/validation_test_runner.py index 91d51c8145..7ee0b3163d 100644 --- a/src/validation_workflow/validation_test_runner.py +++ b/src/validation_workflow/validation_test_runner.py @@ -6,12 +6,14 @@ # compatible open source license. # type: ignore +from validation_workflow.deb.validation_deb import ValidateDeb from validation_workflow.docker.validation_docker import ValidateDocker from validation_workflow.rpm.validation_rpm import ValidateRpm from validation_workflow.tar.validation_tar import ValidateTar from validation_workflow.validation import Validation from validation_workflow.validation_args import ValidationArgs from validation_workflow.yum.validation_yum import ValidateYum +from validation_workflow.zip.validation_zip import ValidateZip class ValidationTestRunner: @@ -19,7 +21,9 @@ class ValidationTestRunner: "docker": ValidateDocker, "tar": ValidateTar, "rpm": ValidateRpm, - "yum": ValidateYum + "yum": ValidateYum, + "zip": ValidateZip, + "deb": ValidateDeb } @classmethod diff --git a/src/validation_workflow/yum/validation_yum.py b/src/validation_workflow/yum/validation_yum.py index 357f1c63b2..ccbcec70b0 100644 --- a/src/validation_workflow/yum/validation_yum.py +++ b/src/validation_workflow/yum/validation_yum.py @@ -7,10 +7,8 @@ import logging import os -import time from system.execute import execute -from system.temporary_directory import TemporaryDirectory from test_workflow.integ_test.utils import get_password from validation_workflow.api_test_cases import ApiTestCases from validation_workflow.download_utils import DownloadUtils @@ -22,28 +20,6 @@ class ValidateYum(Validation, DownloadUtils): def __init__(self, args: ValidationArgs) -> None: super().__init__(args) - self.base_url_production = "https://artifacts.opensearch.org/releases/bundle/" - self.base_url_staging = "https://ci.opensearch.org/ci/dbc/distribution-build-" - self.tmp_dir = TemporaryDirectory() - - def download_artifacts(self) -> bool: - isFilePathEmpty = bool(self.args.file_path) - for project in self.args.projects: - if (isFilePathEmpty): - if ("https:" not in self.args.file_path.get(project)): - self.copy_artifact(self.args.file_path.get(project), str(self.tmp_dir.path)) - else: - self.args.version = self.get_version(self.args.file_path.get(project)) - self.check_url(self.args.file_path.get(project)) - - else: - if (self.args.artifact_type == "staging"): - self.args.file_path[project] = f"{self.base_url_staging}{project}/{self.args.version}/{self.args.build_number[project]}/linux/{self.args.arch}/rpm/dist/{project}/{project}-{self.args.version}.staging.repo" # noqa: E501 - else: - self.args.file_path[project] = f"{self.base_url_production}{project}/{self.args.version[0:1]}.x/{project}-{self.args.version[0:1]}.x.repo" - - self.check_url(self.args.file_path.get(project)) - return True def installation(self) -> bool: try: @@ -63,19 +39,22 @@ def start_cluster(self) -> bool: try: for project in self.args.projects: execute(f'sudo systemctl start {project}', ".") - time.sleep(20) execute(f'sudo systemctl status {project}', ".") except: raise Exception('Failed to Start Cluster') return True def validation(self) -> bool: - test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, self.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if not self.args.force_https else True) # noqa: E501 - if (test_result): - logging.info(f'All tests Pass : {counter}') - return True + if self.check_cluster_readiness(): + test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, + self.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if self.args.allow_http else True) + if (test_result): + logging.info(f'All tests Pass : {counter}') + return True + else: + raise Exception(f'Not all tests Pass : {counter}') else: - raise Exception(f'Not all tests Pass : {counter}') + raise Exception("Cluster is not ready for API test") def cleanup(self) -> bool: try: diff --git a/src/validation_workflow/zip/__init__.py b/src/validation_workflow/zip/__init__.py new file mode 100644 index 0000000000..153f30145e --- /dev/null +++ b/src/validation_workflow/zip/__init__.py @@ -0,0 +1,8 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# +# This page intentionally left blank. diff --git a/src/validation_workflow/zip/validation_zip.py b/src/validation_workflow/zip/validation_zip.py new file mode 100644 index 0000000000..72a36bced0 --- /dev/null +++ b/src/validation_workflow/zip/validation_zip.py @@ -0,0 +1,65 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. + +import logging +import os + +from system.process import Process +from system.zip_file import ZipFile +from test_workflow.integ_test.utils import get_password +from validation_workflow.api_test_cases import ApiTestCases +from validation_workflow.download_utils import DownloadUtils +from validation_workflow.validation import Validation +from validation_workflow.validation_args import ValidationArgs + + +class ValidateZip(Validation, DownloadUtils): + def __init__(self, args: ValidationArgs) -> None: + super().__init__(args) + self.os_process = Process() + self.osd_process = Process() + + def installation(self) -> bool: + try: + for project in self.args.projects: + with ZipFile(os.path.join(self.tmp_dir.path, os.path.basename(self.args.file_path.get(project))), "r") as zip: + zip.extractall(self.tmp_dir.path) + except: + raise Exception("Failed to install OpenSearch/OpenSearch-Dashboards") + return True + + def start_cluster(self) -> bool: + try: + self.os_process.start(f"env OPENSEARCH_INITIAL_ADMIN_PASSWORD={get_password(str(self.args.version))} .\\opensearch-windows-install.bat", + os.path.join(self.tmp_dir.path, f"opensearch-{self.args.version}"), False) + if "opensearch-dashboards" in self.args.projects: + self.osd_process.start(".\\bin\\opensearch-dashboards.bat", os.path.join(self.tmp_dir.path, f"opensearch-dashboards-{self.args.version}"), False) + logging.info("Starting cluster") + except: + raise Exception('Failed to Start Cluster') + return True + + def validation(self) -> bool: + if self.check_cluster_readiness(): + test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, + self.check_for_security_plugin(os.path.join(self.tmp_dir.path, "opensearch")) if self.args.allow_http else True) + if (test_result): + logging.info(f'All tests Pass : {counter}') + return True + else: + raise Exception(f'Not all tests Pass : {counter}') + else: + raise Exception("Cluster is not ready for API test") + + def cleanup(self) -> bool: + try: + self.os_process.terminate() + if ("opensearch-dashboards" in self.args.projects): + self.osd_process.terminate() + except: + raise Exception('Failed to terminate the processes that started OpenSearch and OpenSearch-Dashboards') + return True diff --git a/tests/tests_validation_workflow/test_inspect_docker_image.py b/tests/tests_validation_workflow/test_inspect_docker_image.py index 6f36114fcb..5817edbe23 100644 --- a/tests/tests_validation_workflow/test_inspect_docker_image.py +++ b/tests/tests_validation_workflow/test_inspect_docker_image.py @@ -23,13 +23,14 @@ def setUp(self) -> None: self.prod_image_tag = "1.2.3" self.os_build_number = "1000" self.osd_build_number = "2000" + self.projects = ["opensearch", "opensearch-dashboards"] self.version = "2.4.0" with patch("validation_workflow.docker.inspect_docker_image.ValidationArgs", MagicMock()) as mock_args: mock_args.stg_tag.return_value = "stg_tag" self.inspector = InspectDockerImage(self.image_id, self.image_name, self.prod_image_tag) def test_validation_args_stg_arg(self) -> None: - result = ValidationArgs.stg_tag(self, 'opensearch_dashboards').replace(" ", "") # type: ignore + result = ValidationArgs.stg_tag(self, 'opensearch-dashboards').replace(" ", "") # type: ignore self.assertEqual(result, "2.4.0.2000") result = ValidationArgs.stg_tag(self, 'opensearch').replace(" ", "") # type: ignore self.assertEqual(result, "2.4.0.1000") diff --git a/tests/tests_validation_workflow/test_validation.py b/tests/tests_validation_workflow/test_validation.py index fe238694e3..210f8e45b6 100644 --- a/tests/tests_validation_workflow/test_validation.py +++ b/tests/tests_validation_workflow/test_validation.py @@ -8,7 +8,11 @@ import unittest from unittest.mock import Mock, patch +import requests + from system.temporary_directory import TemporaryDirectory +from validation_workflow.api_request import ApiTest +from validation_workflow.docker.validation_docker import ValidateDocker from validation_workflow.tar.validation_tar import ValidateTar from validation_workflow.validation import Validation from validation_workflow.validation_args import ValidationArgs @@ -19,9 +23,6 @@ def __init__(self, args: ValidationArgs) -> None: super().__init__(args) self.tmp_dir = TemporaryDirectory() - def download_artifacts(self) -> None: - return None - def installation(self) -> None: return None @@ -73,3 +74,64 @@ def test_check_for_security_plugin(self, mock_validation_args: Mock, mock_path_e result = mock_validation.check_for_security_plugin("/tmp/tmkuiuo/opensearch") self.assertTrue(result) + + @patch("time.sleep") + @patch('validation_workflow.validation.Validation.check_http_request') + @patch('validation_workflow.validation.ValidationArgs') + def test_check_cluster_readiness_error(self, mock_validation_args: Mock, mock_check_http: Mock, mock_sleep: Mock) -> None: + mock_validation_args.return_value.version = '1.0.0.1000' + mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.allow_http = False + mock_validation_args.return_value.projects = ["opensearch"] + mock_check_http.return_value = False + + validate_docker = ValidateTar(mock_validation_args.return_value) + result = validate_docker.check_cluster_readiness() + + self.assertFalse(result) + + @patch("time.sleep") + @patch('validation_workflow.validation.ValidationArgs') + @patch.object(ApiTest, "api_get") + def test_check_http_request(self, mock_api_get: Mock, mock_validation_args: Mock, mock_sleep: Mock) -> None: + mock_validation_args.return_value.version = '1.3.13' + mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.allow_http = False + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] + mock_api_get.return_value = (200, "text") + + validate_docker = ValidateTar(mock_validation_args.return_value) + result = validate_docker.check_http_request() + + self.assertTrue(result) + + @patch("time.sleep") + @patch('validation_workflow.validation.ValidationArgs') + @patch.object(ApiTest, "api_get") + def test_check_http_request_error(self, mock_api_get: Mock, mock_validation_args: Mock, mock_sleep: Mock) -> None: + mock_validation_args.return_value.version = '1.3.14' + mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.allow_http = False + mock_validation_args.return_value.projects = ["opensearch"] + mock_api_get.return_value = (400, "text") + + validate_docker = ValidateTar(mock_validation_args.return_value) + result = validate_docker.check_http_request() + + self.assertFalse(result) + + @patch("time.sleep") + @patch('validation_workflow.validation.ValidationArgs') + @patch.object(ApiTest, "api_get") + def test_check_http_request_connection_error(self, mock_api_get: Mock, mock_validation_args: Mock, mock_sleep: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.allow_http = False + mock_validation_args.return_value.projects = ["opensearch"] + mock_api_get.side_effect = requests.exceptions.ConnectionError + + validate_docker = ValidateDocker(mock_validation_args.return_value) + + result = validate_docker.check_http_request() + + self.assertFalse(result) diff --git a/tests/tests_validation_workflow/test_validation_args.py b/tests/tests_validation_workflow/test_validation_args.py index ecc4530c1d..80515bf3a3 100644 --- a/tests/tests_validation_workflow/test_validation_args.py +++ b/tests/tests_validation_workflow/test_validation_args.py @@ -34,7 +34,7 @@ def test_without_arguments(self) -> None: def test_rpm_distribution(self) -> None: self.assertEqual(ValidationArgs().distribution, "rpm") - @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "2.4.0", "--distribution", "docker"]) + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "2.4.0", "--distribution", "docker", "--projects", "opensearch", "--using-staging-artifact-only"]) def test_docker_distribution(self) -> None: self.assertEqual(ValidationArgs().distribution, "docker") self.assertNotEqual(ValidationArgs().distribution, "yum") @@ -67,20 +67,47 @@ def test_file_path(self) -> None: def test_artifact_type(self) -> None: self.assertNotEqual(ValidationArgs().artifact_type, "production") - @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.6", "--distribution", "rpm", "--artifact-type", "staging", "--os-build-number", "1234", "--osd-build-number", "2312", "--force-https"]) # noqa: E501 - def test_force_https(self) -> None: - self.assertEqual(ValidationArgs().force_https, True) + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.6", "--distribution", "rpm", "--artifact-type", "staging", + "--os-build-number", "1234", "--osd-build-number", "2312", "--allow-http"]) + def test_allow_http(self) -> None: + self.assertEqual(ValidationArgs().allow_http, True) + + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.6", "--distribution", "rpm", "--artifact-type", "staging", "--os-build-number", "1234", "--osd-build-number", "2312"]) + def test_do_not_allow_http(self) -> None: + self.assertEqual(ValidationArgs().allow_http, False) @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.0", "--projects", "opensearch"]) def test_set_projects(self) -> None: self.assertEqual(ValidationArgs().projects, ["opensearch"]) + @patch('sys.argv', [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.rpm", + "opensearch-dashboard=https://opensearch.org/releases/opensearch/2.8.0/opensearch-dashboards-2.8.0-linux-x64.rpm"]) + def test_dashboards_exception(self) -> None: + with self.assertRaises(Exception) as ctx: + self.assertEqual(ValidationArgs().distribution, "rpm") + self.assertEqual(str(ctx.exception), "Missing OpenSearch artifact details! Please provide the valid product names among opensearch and opensearch-dashboards") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "2.4.0", "--distribution", "docker", "--os-build-number", "1234", + "--osd-build-number", "8393", "--projects", "opensearch", "--using-staging-artifact-only"]) + def test_docker_exception(self) -> None: + with self.assertRaises(Exception) as ctx: + self.assertEqual(ValidationArgs().projects, ["opensearch"]) + self.assertEqual(ValidationArgs().osd_build_number, "1234") + self.assertEqual(str(ctx.exception), "--osd_build_number argument cannot be provided without specifying opensearch-dashboards in --projects") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "2.4.0", "--distribution", "docker", "--os-build-number", "1234", "--projects", "opensearch"]) + def test_docker_arguments_exception(self) -> None: + with self.assertRaises(Exception) as ctx: + self.assertEqual(ValidationArgs().projects, ["opensearch"]) + self.assertEqual(ValidationArgs().osd_build_number, "1234") + self.assertEqual(str(ctx.exception), "Provide either of --validate-digest-only and --using-staging-artifact-only for Docker Validation") + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch-dashboards=https://opensearch.org/releases/opensearch/2.8.0/opensearch-dashboards-2.8.0-linux-x64.rpm"]) def test_projects_exception(self) -> None: with self.assertRaises(Exception) as ctx: self.assertEqual(ValidationArgs().distribution, "rpm") self.assertEqual(ValidationArgs().projects, ["opensearch-dashboards"]) - self.assertEqual(str(ctx.exception), "Missing OpenSearch OpenSearch artifact details! Please provide the same along with OpenSearch-Dashboards to validate") + self.assertEqual(str(ctx.exception), "Missing OpenSearch artifact details! Please provide the valid product names among opensearch and opensearch-dashboards") @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.xyz"]) def test_file_path_distribution_type(self) -> None: @@ -97,3 +124,13 @@ def test_get_distribution_type_tar(self) -> None: def test_get_distribution_type_yum(self) -> None: result = ValidationArgs().get_distribution_type(ValidationArgs().file_path) self.assertEqual(result, "yum") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch--2.8.0-windows-x64.zip"]) + def test_get_distribution_type_zip(self) -> None: + result = ValidationArgs().get_distribution_type(ValidationArgs().file_path) + self.assertEqual(result, "zip") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-arm64.deb"]) + def test_get_distribution_type_deb(self) -> None: + result = ValidationArgs().get_distribution_type(ValidationArgs().file_path) + self.assertEqual(result, "deb") diff --git a/tests/tests_validation_workflow/test_validation_deb.py b/tests/tests_validation_workflow/test_validation_deb.py new file mode 100644 index 0000000000..cc89d4c600 --- /dev/null +++ b/tests/tests_validation_workflow/test_validation_deb.py @@ -0,0 +1,160 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. + +import unittest +from unittest.mock import MagicMock, Mock, call, patch + +from validation_workflow.deb.validation_deb import ValidateDeb + + +class TestValidateDeb(unittest.TestCase): + def setUp(self) -> None: + self.mock_args = MagicMock() + self.mock_args.version = "2.3.0" + self.mock_args.arch = "x64" + self.mock_args.projects = ["opensearch"] + self.mock_args.file_path = {"opensearch": "/src/opensearch/opensearch-1.3.12.staging.deb"} + self.mock_args.platform = "linux" + self.call_methods = ValidateDeb(self.mock_args) + + @patch("validation_workflow.deb.validation_deb.execute") + @patch('os.path.basename') + @patch("validation_workflow.deb.validation_deb.get_password") + def test_installation(self, mock_get_pwd: Mock, mock_basename: Mock, mock_system: Mock) -> None: + validate_deb = ValidateDeb(self.mock_args) + mock_basename.side_effect = lambda path: "mocked_filename" + mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") + result = validate_deb.installation() + self.assertTrue(result) + mock_get_pwd.assert_called_with("2.3.0") + + @patch("validation_workflow.deb.validation_deb.execute") + @patch("validation_workflow.deb.validation_deb.get_password") + def test_installation_exception_os(self, mock_get_pwd: Mock, mock_execute: Mock) -> None: + validate_deb = ValidateDeb(self.mock_args) + mock_execute.side_effect = Exception("any exception occurred") + with self.assertRaises(Exception) as context: + validate_deb.installation() + + mock_get_pwd.assert_called_with("2.3.0") + self.assertEqual(str(context.exception), "Failed to install OpenSearch/OpenSearch-Dashboards") + + @patch("validation_workflow.deb.validation_deb.execute") + @patch("validation_workflow.deb.validation_deb.ValidationArgs") + def test_start_cluster(self, mock_validation_args: Mock, mock_execute: Mock) -> None: + self.mock_args.projects = ["opensearch", "opensearch-dashboards"] + + validate_deb = ValidateDeb(self.mock_args) + result = validate_deb.start_cluster() + self.assertTrue(result) + mock_execute.assert_has_calls( + [ + call("sudo systemctl enable opensearch", "."), + call("sudo systemctl start opensearch", "."), + call("sudo systemctl status opensearch", "."), + call("sudo systemctl enable opensearch-dashboards", "."), + call("sudo systemctl start opensearch-dashboards", "."), + call("sudo systemctl status opensearch-dashboards", "."), + ] + ) + + @patch("validation_workflow.deb.validation_deb.execute") + def test_start_cluster_exception_os(self, mock_execute: MagicMock) -> None: + validate_deb = ValidateDeb(self.mock_args) + mock_execute.side_effect = Exception("any exception occurred") + with self.assertRaises(Exception) as context: + validate_deb.start_cluster() + + self.assertEqual(str(context.exception), "Failed to Start Cluster") + + @patch("validation_workflow.deb.validation_deb.ApiTestCases") + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation(self, mock_check_cluster: Mock, mock_test_apis: Mock) -> None: + mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True + mock_test_apis_instance.test_apis.return_value = (True, 3) + + validate_deb = ValidateDeb(self.mock_args) + + result = validate_deb.validation() + self.assertTrue(result) + + mock_test_apis.assert_called_once() + mock_check_cluster.assert_called_once() + + @patch('validation_workflow.deb.validation_deb.ApiTestCases') + @patch('os.path.basename') + @patch('validation_workflow.deb.validation_deb.execute') + @patch('validation_workflow.validation.Validation.check_for_security_plugin') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + def test_validation_with_allow_http(self, mock_validation_args: Mock, mock_check_cluster: Mock, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + mock_validation_args.return_value.allow_http = True + validate_deb = ValidateDeb(mock_validation_args.return_value) + mock_check_cluster.return_value = True + mock_basename.side_effect = lambda path: "mocked_filename" + mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") + mock_security.return_value = True + mock_test_apis_instance = mock_test_apis.return_value + mock_test_apis_instance.test_apis.return_value = (True, 4) + + result = validate_deb.validation() + self.assertTrue(result) + mock_check_cluster.assert_called_once() + mock_security.assert_called_once() + + @patch('validation_workflow.deb.validation_deb.ValidationArgs') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_cluster_not_ready(self, mock_check_cluster: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + validate_deb = ValidateDeb(mock_validation_args.return_value) + mock_check_cluster.return_value = False + + with self.assertRaises(Exception) as context: + validate_deb.validation() + self.assertEqual(str(context.exception), 'Cluster is not ready for API test') + mock_check_cluster.assert_called_once() + + @patch("validation_workflow.deb.validation_deb.ApiTestCases") + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_failed_testcases(self, mock_check_cluster: Mock, mock_test_apis: Mock) -> None: + mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True + mock_test_apis_instance.test_apis.return_value = (False, 2) + validate_deb = ValidateDeb(self.mock_args) + + with self.assertRaises(Exception) as context: + validate_deb.validation() + + self.assertEqual(str(context.exception), "Not all tests Pass : 2") + + mock_test_apis.assert_called_once() + + @patch("validation_workflow.deb.validation_deb.execute") + def test_cleanup(self, mock_execute: Mock) -> None: + self.mock_args.projects = ["opensearch", "opensearch-dashboards"] + + validate_deb = ValidateDeb(self.mock_args) + result = validate_deb.cleanup() + self.assertTrue(result) + mock_execute.assert_has_calls( + [call("sudo dpkg --purge opensearch", "."), call("sudo dpkg --purge opensearch-dashboards", ".")] + ) + + @patch("validation_workflow.deb.validation_deb.execute") + def test_cleanup_exception(self, mock_execute: Mock) -> None: + self.mock_args.projects = ["opensearch", "opensearch-dashboards"] + mock_execute.side_effect = Exception("an exception occurred") + validate_deb = ValidateDeb(self.mock_args) + with self.assertRaises(Exception) as context: + validate_deb.cleanup() + + self.assertEqual( + str(context.exception), + "Exception occurred either while attempting to stop cluster or removing OpenSearch/OpenSearch-Dashboards. an exception occurred", + ) diff --git a/tests/tests_validation_workflow/test_validation_docker.py b/tests/tests_validation_workflow/test_validation_docker.py index 2947fe4a8e..7862835b51 100644 --- a/tests/tests_validation_workflow/test_validation_docker.py +++ b/tests/tests_validation_workflow/test_validation_docker.py @@ -5,6 +5,8 @@ # this file be licensed under the Apache-2.0 license or a # compatible open source license. +import os +import shutil import subprocess import unittest import urllib.request @@ -43,13 +45,11 @@ def test_download_artifacts(self, mock_is_container_daemon_running: Mock, mock_v @patch('validation_workflow.docker.validation_docker.ApiTestCases') @patch('validation_workflow.docker.validation_docker.ValidateDocker.run_container') @patch('validation_workflow.docker.validation_docker.InspectDockerImage.inspect_digest') - @patch('time.sleep', return_value=None) - def test_staging(self, mock_time_sleep: Mock, mock_digest: Mock, mock_container: Mock, mock_test: Mock, mock_docker_image: Mock, mock_validation_args: Mock, mock_check_http: Mock) -> None: + def test_staging(self, mock_digest: Mock, mock_container: Mock, mock_test: Mock, mock_docker_image: Mock, mock_validation_args: Mock, mock_check_http: Mock) -> None: # Set up mock objects - mock_validation_args.return_value.OS_image = 'opensearchstaging/opensearch-os' mock_validation_args.return_value.version = '1.0.0.1000' mock_validation_args.return_value.validate_digest_only = False - mock_validation_args.return_value.allow_without_security = False + mock_validation_args.return_value.allow_http = False mock_validation_args.return_value.projects = ["opensearch"] mock_docker_image.return_value = MagicMock() mock_container.return_value = (True, 'test_file.yml') @@ -72,16 +72,54 @@ def test_staging(self, mock_time_sleep: Mock, mock_digest: Mock, mock_container: mock_test.assert_called_once() mock_test.assert_has_calls([call(), call().test_apis("1.0.0.1000", ['opensearch'], True)]) + @patch('validation_workflow.docker.validation_docker.ValidateDocker.check_cluster_readiness') + @patch('validation_workflow.docker.validation_docker.ValidationArgs') + @patch('validation_workflow.docker.validation_docker.ValidateDocker.run_container') + def test_staging_cluster_not_ready(self, mock_container: Mock, mock_validation_args: Mock, + mock_cluster_readiness: Mock) -> None: + mock_validation_args.return_value.version = '1.0.0.1000' + mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.allow_http = False + mock_validation_args.return_value.projects = ["opensearch"] + mock_cluster_readiness.return_value = False + mock_container.return_value = (True, 'test_file.yml') + + validate_docker = ValidateDocker(mock_validation_args.return_value) + validate_docker.image_ids = {'opensearch': 'images_id_0'} + validate_docker.replacements = [('opensearchproject/opensearch:1', 'images_id_0')] + + with self.assertRaises(Exception) as context: + validate_docker.validation() + self.assertEqual(str(context.exception), 'Cluster is not ready for API test.') + mock_cluster_readiness.assert_called_once() + + @patch('validation_workflow.docker.validation_docker.ValidationArgs') + @patch('validation_workflow.docker.validation_docker.ValidateDocker.run_container') + def test_container_startup_exception(self, mock_container: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '1.0.0.1000' + mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.allow_http = False + mock_validation_args.return_value.projects = ["opensearch"] + mock_container.return_value = (False, 'test_file.yml') + + # Create instance of ValidateDocker class + validate_docker = ValidateDocker(mock_validation_args.return_value) + validate_docker.image_ids = {'opensearch': 'images_id_0'} + validate_docker.replacements = [('opensearchproject/opensearch:1', 'images_id_0')] + + with self.assertRaises(Exception) as context: + validate_docker.validation() + self.assertEqual(str(context.exception), 'The container failed to start. Exiting the validation.') + mock_container.assert_called_once() + @patch('validation_workflow.docker.validation_docker.ValidateDocker.check_http_request') @patch('validation_workflow.docker.validation_docker.ValidationArgs') @patch('validation_workflow.docker.validation_docker.InspectDockerImage') @patch('validation_workflow.docker.validation_docker.ApiTestCases') @patch('validation_workflow.docker.validation_docker.ValidateDocker.run_container') @patch('validation_workflow.docker.validation_docker.InspectDockerImage.inspect_digest') - @patch('time.sleep', return_value=None) - def test_digests(self, mock_time_sleep: Mock, mock_digest: Mock, mock_container: Mock, mock_test: Mock, mock_docker_image: Mock, mock_validation_args: Mock, mock_check_http: Mock) -> None: + def test_digests(self, mock_digest: Mock, mock_container: Mock, mock_test: Mock, mock_docker_image: Mock, mock_validation_args: Mock, mock_check_http: Mock) -> None: # Set up mock objects - mock_validation_args.return_value.OS_image = 'opensearchstaging/opensearch-os' mock_validation_args.return_value.version = '1.0.0.1000' mock_validation_args.return_value.using_staging_artifact_only = False mock_validation_args.return_value.validate_digest_only = True @@ -126,7 +164,7 @@ def test_cleanup_process(self, mock_os_remove: Mock, mock_subprocess_run: Mock, # Set up mock objects mock_validation_args.return_value = 'validation_args' - mock_subprocess_run.return_value = subprocess.CompletedProcess(['command'], 0) + mock_subprocess_run.return_value = subprocess.CompletedProcess(args='docker-compose -f docker-compose.yml down', returncode=0, stdout=b'', stderr=b'') # Create instance of class validate_docker = ValidateDocker(mock_validation_args) @@ -168,46 +206,25 @@ def test_docker_compose_files_exist(self) -> None: self.assertTrue(urllib.request.urlopen(docker_compose_file_v1_url).getcode() == 200) self.assertTrue(urllib.request.urlopen(docker_compose_file_v2_url).getcode() == 200) - # @patch('validation_workflow.docker.validation_docker.ValidateDocker.check_http_request') + @patch.dict('os.environ', {'OPENSEARCH_INITIAL_ADMIN_PASSWORD': 'admin'}) + @patch.object(shutil, "copy2") + @patch.object(subprocess, "check_output") + @patch.object(subprocess, "run") + @patch('validation_workflow.docker.validation_docker.get_password') + @patch('validation_workflow.docker.validation_docker.ValidateDocker.inplace_change') @patch('validation_workflow.docker.validation_docker.ValidationArgs') - @patch('validation_workflow.docker.validation_docker.ApiTest.api_get') - def test_check_http_request_success(self, mock_api_test: MagicMock, mock_validation_args: MagicMock) -> None: - mock_validation_args.return_value.test_readiness_urls = { - 'https://localhost:9200/': 'opensearch cluster API', - 'http://localhost:5601/api/status': 'opensearch-dashboards API', - } + def test_run_container(self, mock_validation_args: Mock, mock_inplace: Mock, mock_password: Mock, mock_subprocess_run: MagicMock, + mock_check_output: MagicMock, mock_copy2: MagicMock) -> None: + image_ids = {"opensearch": "sha1", "opensearch-dashboards": "sha2"} + mock_validation_args.return_value.projects = ["opensearch"] + mock_subprocess_run.return_value = subprocess.CompletedProcess(args='docker-compose -f docker-compose.yml down', returncode=0, stdout=b'', stderr=b'') + mock_password.return_value = "admin" mock_validation_args.return_value.version = '1.0.0' - - mock_api_test.return_value = (200, "response") - validate_docker = ValidateDocker(mock_validation_args.return_value) - - validate_docker.args.docker_source = 'dockerhub' - - result = validate_docker.check_http_request() - + result, self._target_yml_file = validate_docker.run_container(image_ids, "2.11.0") self.assertEqual(result, True) - mock_api_test.assert_called() - - @patch('validation_workflow.docker.validation_docker.ValidationArgs') - @patch('validation_workflow.docker.validation_docker.ApiTest.api_get') - def test_check_http_request_failure(self, mock_api_test: MagicMock, mock_validation_args: MagicMock) -> None: - mock_validation_args.return_value.test_readiness_urls = { - 'https://localhost:9200/': 'opensearch cluster API', - 'http://localhost:5601/api/status': 'opensearch-dashboards API', - } - mock_validation_args.return_value.version = '1.0.0' - - mock_api_test.return_value = (400, "response") - - validate_docker = ValidateDocker(mock_validation_args.return_value) - - validate_docker.args.docker_source = 'dockerhub' - - result = validate_docker.check_http_request() - - self.assertEqual(result, False) - mock_api_test.assert_called() + mock_subprocess_run.assert_called_with(os.path.join(f'docker-compose -f {validate_docker.tmp_dir.path}', 'docker-compose.yml up -d opensearch-node1 opensearch-node2'), + shell=True, stdout=-1, stderr=-1, universal_newlines=True) if __name__ == '__main__': diff --git a/tests/tests_validation_workflow/test_validation_rpm.py b/tests/tests_validation_workflow/test_validation_rpm.py index 5ad6b20caf..b30b1ae3c2 100644 --- a/tests/tests_validation_workflow/test_validation_rpm.py +++ b/tests/tests_validation_workflow/test_validation_rpm.py @@ -19,6 +19,7 @@ def setUp(self) -> None: def test_empty_file_path_and_production_artifact_type(self) -> None: self.args.projects = ["opensearch"] self.args.version = "2.4.0" + self.args.distribution = "rpm" self.args.file_path = {} self.args.artifact_type = "production" @@ -41,6 +42,7 @@ def test_with_file_path_both_artifact_types(self) -> None: def test_empty_file_path_and_staging_artifact_type(self, mock_validation_args: Mock) -> None: self.args.projects = ["opensearch"] self.args.version = "2.4.0" + self.args.distribution = "rpm" self.args.artifact_type = "staging" self.args.file_path = {} self.args.build_number = {"opensearch": "1.2.3", "opensearch-dashboards": "1.2.3"} @@ -82,7 +84,8 @@ def test_exceptions(self, mock_validation_args: Mock) -> None: mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] validate_rpm = ValidateRpm(mock_validation_args.return_value) validate_rpm.cleanup() - self.assertIn("Exception occurred either while attempting to stop cluster or removing OpenSearch/OpenSearch-Dashboards.", str(e3.exception)) # noqa: E501 + self.assertIn("Exception occurred either while attempting to stop cluster or removing OpenSearch/OpenSearch-Dashboards.", + str(e3.exception)) @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') @patch("validation_workflow.rpm.validation_rpm.execute") @@ -90,7 +93,7 @@ def test_installation(self, mock_system: Mock, mock_validation_args: Mock) -> No mock_validation_args.return_value.version = '2.3.0' mock_validation_args.return_value.arch = 'x64' mock_validation_args.return_value.platform = 'linux' - mock_validation_args.return_value.force_https = True + mock_validation_args.return_value.allow_http = True mock_validation_args.return_value.projects = ["opensearch"] validate_rpm = ValidateRpm(mock_validation_args.return_value) @@ -100,8 +103,7 @@ def test_installation(self, mock_system: Mock, mock_validation_args: Mock) -> No @patch("validation_workflow.rpm.validation_rpm.execute", return_value=True) @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') - @patch('time.sleep') - def test_start_cluster(self, mock_validation_args: Mock, mock_system: Mock, mock_sleep: Mock) -> None: + def test_start_cluster(self, mock_validation_args: Mock, mock_system: Mock) -> None: mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] validate_rpm = ValidateRpm(mock_validation_args.return_value) @@ -111,26 +113,31 @@ def test_start_cluster(self, mock_validation_args: Mock, mock_system: Mock, mock @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') @patch('validation_workflow.rpm.validation_rpm.ApiTestCases') - def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: - # Set up mock objects + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation(self, mock_check_cluster: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_test_apis_instance = mock_test_apis.return_value - mock_test_apis_instance.test_apis.return_value = (True, 4) + mock_check_cluster.return_value = True + mock_test_apis_instance.test_apis.return_value = (True, 3) validate_rpm = ValidateRpm(mock_validation_args.return_value) - validate_rpm.validation() - self.assertEqual(mock_test_apis.call_count, 1) + result = validate_rpm.validation() + self.assertTrue(result) + mock_check_cluster.assert_called_once() + mock_test_apis.assert_called_once() @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') @patch('validation_workflow.rpm.validation_rpm.ApiTestCases') @patch('os.path.basename') @patch('validation_workflow.rpm.validation_rpm.execute') @patch('validation_workflow.validation.Validation.check_for_security_plugin') - def test_validation_with_security_parameter(self, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation_with_allow_http_check(self, mock_check_cluster: Mock, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' - mock_validation_args.return_value.force_https = False + mock_validation_args.return_value.allow_http = True validate_rpm = ValidateRpm(mock_validation_args.return_value) + mock_check_cluster.return_value = True mock_basename.side_effect = lambda path: "mocked_filename" mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") mock_security.return_value = True @@ -139,17 +146,32 @@ def test_validation_with_security_parameter(self, mock_security: Mock, mock_syst result = validate_rpm.validation() self.assertTrue(result) + mock_check_cluster.assert_called_once() mock_security.assert_called_once() + @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_cluster_not_ready(self, mock_check_cluster: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + validate_rpm = ValidateRpm(mock_validation_args.return_value) + mock_check_cluster.return_value = False + + with self.assertRaises(Exception) as context: + validate_rpm.validation() + self.assertEqual(str(context.exception), 'Cluster is not ready for API test') + mock_check_cluster.assert_called_once() + @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') @patch('validation_workflow.rpm.validation_rpm.ApiTestCases') - def test_failed_testcases(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: - # Set up mock objects + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_failed_testcases(self, mock_check_cluster: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True mock_test_apis_instance.test_apis.return_value = (False, 1) validate_rpm = ValidateRpm(mock_validation_args.return_value) + with self.assertRaises(Exception) as context: validate_rpm.validation() diff --git a/tests/tests_validation_workflow/test_validation_tar.py b/tests/tests_validation_workflow/test_validation_tar.py index 555ec04a74..4364d61d5f 100644 --- a/tests/tests_validation_workflow/test_validation_tar.py +++ b/tests/tests_validation_workflow/test_validation_tar.py @@ -20,6 +20,7 @@ def setUp(self) -> None: def test_empty_file_path_and_production_artifact_type(self) -> None: self.args.projects = ["opensearch"] self.args.version = "2.4.0" + self.args.distribution = "tar" self.args.file_path = {} self.args.artifact_type = "production" @@ -42,6 +43,7 @@ def test_with_file_path_both_artifact_types(self) -> None: def test_empty_file_path_and_staging_artifact_type(self, mock_validation_args: Mock) -> None: self.args.projects = ["opensearch"] self.args.version = "2.4.0" + self.args.distribution = "tar" self.args.artifact_type = "staging" self.args.file_path = {} self.args.build_number = {"opensearch": "latest", "opensearch-dashboards": "latest"} @@ -71,7 +73,7 @@ def test_installation(self, mock_system: Mock, mock_basename: Mock, mock_validat mock_validation_args.return_value.version = '2.3.0' mock_validation_args.return_value.arch = 'x64' mock_validation_args.return_value.platform = 'linux' - mock_validation_args.return_value.force_https = True + mock_validation_args.return_value.allow_http = True mock_validation_args.return_value.projects = ["opensearch"] validate_tar = ValidateTar(mock_validation_args.return_value) @@ -82,13 +84,12 @@ def test_installation(self, mock_system: Mock, mock_basename: Mock, mock_validat @patch('validation_workflow.tar.validation_tar.ValidationArgs') @patch.object(Process, 'start') - @patch('time.sleep') @patch('validation_workflow.tar.validation_tar.get_password') - def test_start_cluster(self, mock_password: Mock, mock_sleep: Mock, mock_start: Mock, mock_validation_args: Mock) -> None: + def test_start_cluster(self, mock_password: Mock, mock_start: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_validation_args.return_value.arch = 'x64' mock_validation_args.return_value.platforms = 'linux' - mock_validation_args.return_value.allow_without_security = True + mock_validation_args.return_value.allow_http = True mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] mock_password.return_value = "admin" @@ -102,7 +103,7 @@ def test_start_cluster(self, mock_password: Mock, mock_sleep: Mock, mock_start: @patch('src.test_workflow.integ_test.utils.get_password') def test_start_cluster_exception_os(self, mock_password: Mock, mock_sleep: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.projects = ["opensearch"] - mock_validation_args.return_value.allow_without_security = True + mock_validation_args.return_value.allow_http = True validate_tar = ValidateTar(mock_validation_args.return_value) validate_tar.os_process.start = MagicMock(side_effect=Exception('Failed to Start Cluster')) # type: ignore @@ -113,16 +114,18 @@ def test_start_cluster_exception_os(self, mock_password: Mock, mock_sleep: Mock, @patch('validation_workflow.tar.validation_tar.ValidationArgs') @patch('validation_workflow.tar.validation_tar.ApiTestCases') - def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation(self, mock_check_cluster: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True mock_test_apis_instance.test_apis.return_value = (True, 3) validate_tar = ValidateTar(mock_validation_args.return_value) result = validate_tar.validation() self.assertTrue(result) - + mock_check_cluster.assert_called_once() mock_test_apis.assert_called_once() @patch('validation_workflow.tar.validation_tar.ValidationArgs') @@ -130,10 +133,12 @@ def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> N @patch('os.path.basename') @patch('validation_workflow.tar.validation_tar.execute') @patch('validation_workflow.validation.Validation.check_for_security_plugin') - def test_validation_without_force_https_check(self, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation_with_allow_http(self, mock_check_cluster: Mock, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' - mock_validation_args.return_value.force_https = False + mock_validation_args.return_value.allow_http = True validate_tar = ValidateTar(mock_validation_args.return_value) + mock_check_cluster.return_value = True mock_basename.side_effect = lambda path: "mocked_filename" mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") mock_security.return_value = True @@ -142,26 +147,37 @@ def test_validation_without_force_https_check(self, mock_security: Mock, mock_sy result = validate_tar.validation() self.assertTrue(result) + mock_check_cluster.assert_called_once() mock_security.assert_called_once() + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_cluster_not_ready(self, mock_check_cluster: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + validate_tar = ValidateTar(mock_validation_args.return_value) + mock_check_cluster.return_value = False + + with self.assertRaises(Exception) as context: + validate_tar.validation() + self.assertEqual(str(context.exception), 'Cluster is not ready for API test') + mock_check_cluster.assert_called_once() + @patch('validation_workflow.tar.validation_tar.ValidationArgs') @patch('validation_workflow.tar.validation_tar.ApiTestCases') - def test_failed_testcases(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: - # Set up mock objects + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_failed_testcases(self, mock_check_cluster: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True mock_test_apis_instance.test_apis.return_value = (False, 1) - # Create instance of ValidateTar class validate_tar = ValidateTar(mock_validation_args.return_value) - # Call validation method and assert the result with self.assertRaises(Exception) as context: validate_tar.validation() self.assertEqual(str(context.exception), 'Not all tests Pass : 1') - # Assert that the mock methods are called as expected mock_test_apis.assert_called_once() @patch('validation_workflow.tar.validation_tar.ValidationArgs') diff --git a/tests/tests_validation_workflow/test_validation_yum.py b/tests/tests_validation_workflow/test_validation_yum.py index e266f52259..455d12ffbc 100644 --- a/tests/tests_validation_workflow/test_validation_yum.py +++ b/tests/tests_validation_workflow/test_validation_yum.py @@ -20,6 +20,7 @@ def setUp(self) -> None: def test_empty_file_path_and_production_artifact_type(self) -> None: self.args.projects = ["opensearch"] self.args.version = "2.5.0" + self.args.distribution = "yum" self.args.file_path = {} self.args.artifact_type = "production" @@ -43,6 +44,7 @@ def test_empty_file_path_and_staging_artifact_type(self, mock_validation_args: M self.args.projects = ["opensearch"] self.args.version = "2.4.0" self.args.artifact_type = "staging" + self.args.distribution = "yum" self.args.file_path = {} self.args.build_number = {"opensearch": "1.2.3", "opensearch-dashboards": "1.2.3"} @@ -83,14 +85,15 @@ def test_exceptions(self, mock_validation_args: Mock) -> None: mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] validate_yum = ValidateYum(mock_validation_args.return_value) validate_yum.cleanup() - self.assertIn("Exception occurred either while attempting to stop cluster or removing OpenSearch/OpenSearch-Dashboards.", str(e3.exception)) # noqa: E501 + self.assertIn("Exception occurred either while attempting to stop cluster or removing OpenSearch/OpenSearch-Dashboards.", + str(e3.exception)) @patch("validation_workflow.yum.validation_yum.execute") @patch('validation_workflow.yum.validation_yum.ValidationArgs') def test_installation(self, mock_validation_args: Mock, mock_execute: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_validation_args.return_value.arch = 'x64' - mock_validation_args.return_value.allow_without_security = False + mock_validation_args.return_value.allow_http = False mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] mock_execute.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") @@ -101,8 +104,7 @@ def test_installation(self, mock_validation_args: Mock, mock_execute: Mock) -> N @patch("validation_workflow.yum.validation_yum.execute", return_value=True) @patch('validation_workflow.yum.validation_yum.ValidationArgs') - @patch('time.sleep') - def test_start_cluster(self, mock_validation_args: Mock, mock_execute: Mock, mock_sleep: Mock) -> None: + def test_start_cluster(self, mock_validation_args: Mock, mock_execute: Mock) -> None: mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] validate_yum = ValidateYum(mock_validation_args.return_value) @@ -112,16 +114,18 @@ def test_start_cluster(self, mock_validation_args: Mock, mock_execute: Mock, moc @patch('validation_workflow.yum.validation_yum.ValidationArgs') @patch('validation_workflow.yum.validation_yum.ApiTestCases') - def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation(self, mock_check_cluster: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_test_apis_instance = mock_test_apis.return_value - mock_test_apis_instance.test_apis.return_value = (True, 4) + mock_check_cluster.return_value = True + mock_test_apis_instance.test_apis.return_value = (True, 3) validate_yum = ValidateYum(mock_validation_args.return_value) result = validate_yum.validation() self.assertTrue(result) - + mock_check_cluster.assert_called_once() mock_test_apis.assert_called_once() @patch('validation_workflow.yum.validation_yum.ValidationArgs') @@ -129,10 +133,12 @@ def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> N @patch('os.path.basename') @patch('validation_workflow.yum.validation_yum.execute') @patch('validation_workflow.validation.Validation.check_for_security_plugin') - def test_validation_without_force_https_check(self, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation_with_allow_http(self, mock_check_cluster: Mock, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' - mock_validation_args.return_value.force_https = False + mock_validation_args.return_value.allow_http = True validate_yum = ValidateYum(mock_validation_args.return_value) + mock_check_cluster.return_value = True mock_basename.side_effect = lambda path: "mocked_filename" mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") mock_security.return_value = True @@ -141,20 +147,37 @@ def test_validation_without_force_https_check(self, mock_security: Mock, mock_sy result = validate_yum.validation() self.assertTrue(result) + mock_check_cluster.assert_called_once() mock_security.assert_called_once() + @patch('validation_workflow.yum.validation_yum.ValidationArgs') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_cluster_not_ready(self, mock_check_cluster: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + validate_yum = ValidateYum(mock_validation_args.return_value) + mock_check_cluster.return_value = False + + with self.assertRaises(Exception) as context: + validate_yum.validation() + self.assertEqual(str(context.exception), 'Cluster is not ready for API test') + mock_check_cluster.assert_called_once() + @patch('validation_workflow.yum.validation_yum.ValidationArgs') @patch('validation_workflow.yum.validation_yum.ApiTestCases') - def test_failed_testcases(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_failed_testcases(self, mock_check_cluster: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True mock_test_apis_instance.test_apis.return_value = (False, 1) validate_yum = ValidateYum(mock_validation_args.return_value) with self.assertRaises(Exception) as context: validate_yum.validation() + self.assertEqual(str(context.exception), 'Not all tests Pass : 1') + mock_test_apis.assert_called_once() @patch("validation_workflow.yum.validation_yum.execute", return_value=True) diff --git a/tests/tests_validation_workflow/test_validation_zip.py b/tests/tests_validation_workflow/test_validation_zip.py new file mode 100644 index 0000000000..31a76d9c23 --- /dev/null +++ b/tests/tests_validation_workflow/test_validation_zip.py @@ -0,0 +1,146 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. + +import unittest +from unittest.mock import MagicMock, Mock, call, patch + +from system.process import Process +from validation_workflow.zip.validation_zip import ValidateZip + + +class TestValidateZip(unittest.TestCase): + def setUp(self) -> None: + self.mock_args = MagicMock() + self.mock_args.projects = ["opensearch", "opensearch-dashboards"] + self.mock_args.version = "2.11.0" + self.mock_args.arch = "x64" + self.mock_args.platform = "windows" + self.mock_args.force_https_check = True + self.mock_args.allow_http = True + self.call_methods = ValidateZip(self.mock_args) + + @patch("validation_workflow.zip.validation_zip.ZipFile") + @patch('os.path.basename') + def test_installation(self, mock_basename: Mock, mock_zip_file: MagicMock) -> None: + mock_zip_file_instance = mock_zip_file.return_value.__enter__() + mock_extractall = MagicMock() + mock_zip_file_instance.extractall = mock_extractall + mock_basename.side_effect = lambda path: "mocked_filename" + + validate_zip = ValidateZip(self.mock_args) + result = validate_zip.installation() + self.assertTrue(result) + + expected_calls = [call((validate_zip.tmp_dir.path))] * len(self.mock_args.projects) + mock_extractall.assert_has_calls(expected_calls) + + @patch("validation_workflow.zip.validation_zip.ZipFile") + @patch('os.path.basename') + def test_installation_exception(self, mock_basename: Mock, mock_zip_file: MagicMock) -> None: + self.mock_args.projects = None + validate_zip = ValidateZip(self.mock_args) + mock_basename.side_effect = lambda path: "mocked_filename" + with self.assertRaises(Exception) as context: + validate_zip.installation() + self.assertEqual(str(context.exception), "Failed to install OpenSearch/OpenSearch-Dashboards") + + @patch.object(Process, "start") + @patch('validation_workflow.zip.validation_zip.get_password') + def test_start_cluster(self, mock_password: Mock, mock_start: Mock) -> None: + + validate_zip = ValidateZip(self.mock_args) + mock_password.return_value = "admin" + result = validate_zip.start_cluster() + self.assertTrue(result) + mock_password.assert_called_once() + + @patch.object(Process, "start") + def test_start_cluster_exception(self, mock_start: Mock) -> None: + mock_start.side_effect = Exception("an exception") + validate_zip = ValidateZip(self.mock_args) + with self.assertRaises(Exception) as context: + validate_zip.start_cluster() + self.assertEqual(str(context.exception), "Failed to Start Cluster") + + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + @patch("validation_workflow.zip.validation_zip.ApiTestCases") + def test_validation(self, mock_test_apis: MagicMock, mock_check_cluster: Mock) -> None: + mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True + mock_test_apis_instance.test_apis.return_value = (True, 3) + validate_zip = ValidateZip(self.mock_args) + result = validate_zip.validation() + + self.assertTrue(result) + mock_check_cluster.assert_called_once() + mock_test_apis.assert_called_once() + + @patch('validation_workflow.zip.validation_zip.ValidationArgs') + @patch('validation_workflow.zip.validation_zip.ApiTestCases') + @patch('os.path.basename') + @patch('system.execute.execute') + @patch('validation_workflow.validation.Validation.check_for_security_plugin') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_validation_with_allow_http(self, mock_check_cluster: Mock, mock_security: Mock, mock_system: Mock, mock_basename: Mock, mock_test_apis: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + mock_validation_args.return_value.allow_http = True + validate_zip = ValidateZip(mock_validation_args.return_value) + mock_check_cluster.return_value = True + mock_basename.side_effect = lambda path: "mocked_filename" + mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") + mock_security.return_value = True + mock_test_apis_instance = mock_test_apis.return_value + mock_test_apis_instance.test_apis.return_value = (True, 4) + + result = validate_zip.validation() + self.assertTrue(result) + mock_check_cluster.assert_called_once() + mock_security.assert_called_once() + + @patch('validation_workflow.zip.validation_zip.ValidationArgs') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_cluster_not_ready(self, mock_check_cluster: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.version = '2.3.0' + validate_zip = ValidateZip(mock_validation_args.return_value) + mock_check_cluster.return_value = False + + with self.assertRaises(Exception) as context: + validate_zip.validation() + self.assertEqual(str(context.exception), 'Cluster is not ready for API test') + mock_check_cluster.assert_called_once() + + @patch('validation_workflow.zip.validation_zip.ApiTestCases') + @patch('validation_workflow.validation.Validation.check_cluster_readiness') + def test_failed_testcases(self, mock_check_cluster: Mock, mock_test_apis: Mock) -> None: + mock_test_apis_instance = mock_test_apis.return_value + mock_check_cluster.return_value = True + mock_test_apis_instance.test_apis.return_value = (False, 1) + + validate_zip = ValidateZip(self.mock_args) + with self.assertRaises(Exception) as context: + validate_zip.validation() + + self.assertEqual(str(context.exception), 'Not all tests Pass : 1') + mock_test_apis.assert_called_once() + + @patch.object(Process, "terminate") + def test_cleanup(self, mock_process_terminate: MagicMock) -> None: + validate_zip = ValidateZip(self.mock_args) + result = validate_zip.cleanup() + self.assertTrue(result) + mock_process_terminate.assert_called() + + @patch.object(Process, "terminate") + def test_cleanup_exception(self, mock_process_terminate: MagicMock) -> None: + mock_process_terminate.side_effect = Exception("any exception") + validate_zip = ValidateZip(self.mock_args) + with self.assertRaises(Exception) as context: + validate_zip.cleanup() + self.assertEqual( + str(context.exception), + "Failed to terminate the processes that started OpenSearch and OpenSearch-Dashboards", + )