From 464254d085cd38eb7d4af41909e0b192a25fcc17 Mon Sep 17 00:00:00 2001 From: Divya Madala <113469545+Divyaasm@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:32:28 -0700 Subject: [PATCH] Validation Workflow enhancement to support run with different options for staging and local artifacts (#3993) Signed-off-by: Divya Madala --- src/validation_workflow/api_test_cases.py | 22 +-- .../docker/validation_docker.py | 29 ++-- src/validation_workflow/download_utils.py | 2 +- src/validation_workflow/rpm/validation_rpm.py | 33 +++-- src/validation_workflow/tar/validation_tar.py | 41 +++--- src/validation_workflow/validation.py | 23 ++- src/validation_workflow/validation_args.py | 62 +++++++- src/validation_workflow/yum/validation_yum.py | 34 +++-- .../test_api_test_cases.py | 21 ++- .../test_download_utils.py | 52 +++++++ .../test_validation.py | 63 ++++++++ .../test_validation_args.py | 42 ++++++ .../test_validation_docker.py | 47 +++++- .../test_validation_rpm.py | 123 ++++++++++++---- .../test_validation_tar.py | 139 ++++++++++++++---- .../test_validation_yum.py | 119 +++++++++++---- 16 files changed, 673 insertions(+), 179 deletions(-) create mode 100644 tests/tests_validation_workflow/test_download_utils.py create mode 100644 tests/tests_validation_workflow/test_validation.py diff --git a/src/validation_workflow/api_test_cases.py b/src/validation_workflow/api_test_cases.py index fe92eaad58..d2e5f553f3 100644 --- a/src/validation_workflow/api_test_cases.py +++ b/src/validation_workflow/api_test_cases.py @@ -9,7 +9,6 @@ from typing import Any from validation_workflow.api_request import ApiTest -from validation_workflow.validation_args import ValidationArgs ''' This class is the collection of test cases to run. @@ -19,24 +18,25 @@ class ApiTestCases: def __init__(self) -> None: - self.opensearch_image_version = ValidationArgs().stg_tag('opensearch').split(' ')[0] - self.opensearch_dashboards_image_version = ValidationArgs().stg_tag('opensearch_dashboards').split(' ')[0] + pass - def test_cases(self) -> Any: + @staticmethod + def test_apis(projects: list) -> Any: pass_counter, fail_counter = 0, 0 # the test case parameters are formated as ['',,''] - test_cases = [ - ['https://localhost:9200/', 200, '"number" : "' + self.opensearch_image_version + '"'], + test_apis = [ + ['https://localhost:9200/', 200, ''], ['https://localhost:9200/_cat/plugins?v', 200, ''], ['https://localhost:9200/_cat/health?v', 200, 'green'], - ['http://localhost:5601/api/status', 200, '"number":"' + self.opensearch_dashboards_image_version + '"'] ] + if ("opensearch-dashboards" in projects): + test_apis.append(['http://localhost:5601/api/status', 200, '']) - for test_case in test_cases: - request_url = test_case.__getitem__(0) - success_status_code = test_case.__getitem__(1) - validate_string = test_case.__getitem__(2) + for test_api in test_apis: + request_url = test_api.__getitem__(0) + success_status_code = test_api.__getitem__(1) + validate_string = test_api.__getitem__(2) status_code, response_text = ApiTest(str(request_url)).api_get() logging.info(f"\nRequest_url ->{str(request_url)} \n") diff --git a/src/validation_workflow/docker/validation_docker.py b/src/validation_workflow/docker/validation_docker.py index 0757dd84c6..540a958641 100644 --- a/src/validation_workflow/docker/validation_docker.py +++ b/src/validation_workflow/docker/validation_docker.py @@ -33,14 +33,14 @@ def download_artifacts(self) -> bool: assert self.is_container_daemon_running(), 'Docker daemon is not running. Exiting the docker validation.' # STEP 1 . pull the images for OS and OSD - product_names = ["opensearch", "opensearch_dashboards"] + product_names = self.args.projects using_staging_artifact_only = 'staging' if self.args.using_staging_artifact_only else 'production' get_image_id = lambda product: self.get_image_id( # noqa: E731 self.get_artifact_image_name(product, using_staging_artifact_only), self.args.version if not self.args.using_staging_artifact_only else ValidationArgs().stg_tag(product).replace(" ", "")) - self.image_ids = list(map(get_image_id, product_names)) - logging.info(f'the opensearch image ID is : {self.image_ids[0]}') - logging.info(f'the opensearch-dashboards image ID is : {self.image_ids[1]} \n\n') + self.image_ids = {key: value for key, value in zip(product_names, list(map(get_image_id, product_names)))} + self.image_ids = {key: value.strip() for key, value in self.image_ids.items()} + return True except AssertionError as e: @@ -62,8 +62,8 @@ 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_digests = list(map(lambda x: self.inspect_docker_image(x[0], x[1]), zip(self.image_ids, self.image_names_list))) # type: ignore - + self.image_names_list = [x for x in self.image_names_list if (os.path.basename(x) 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') if self.args.validate_digest_only: @@ -75,8 +75,7 @@ def validation(self) -> bool: # STEP 3 . spin-up OS/OSD cluster if not self.args.validate_digest_only: return_code, self._target_yml_file = self.run_container( - self.image_ids[0], - self.image_ids[1], + self.image_ids, self.args.version ) if return_code: @@ -84,7 +83,7 @@ def validation(self) -> bool: if self.check_cluster_readiness(): # STEP 4 . OS, OSD API validation - _test_result, _counter = ApiTestCases().test_cases() + _test_result, _counter = ApiTestCases().test_apis(self.args.projects) if _test_result: logging.info(f'All tests Pass : {_counter}') @@ -167,7 +166,7 @@ def get_artifact_image_name(self, artifact: str, using_staging_artifact_only: st 'staging': 'opensearchstaging/opensearch', 'production': 'opensearchproject/opensearch' }, - 'opensearch_dashboards': { + 'opensearch-dashboards': { 'staging': 'opensearchstaging/opensearch-dashboards', 'production': 'opensearchproject/opensearch-dashboards' } @@ -177,7 +176,7 @@ def get_artifact_image_name(self, artifact: str, using_staging_artifact_only: st 'staging': 'public.ecr.aws/opensearchstaging/opensearch', 'production': 'public.ecr.aws/opensearchproject/opensearch' }, - 'opensearch_dashboards': { + 'opensearch-dashboards': { 'staging': 'public.ecr.aws/opensearchstaging/opensearch-dashboards', 'production': 'public.ecr.aws/opensearchproject/opensearch-dashboards' } @@ -236,7 +235,7 @@ def pull_image(self, image_name: str, image_version: str) -> str: else: raise Exception(f'error on pulling image : return code {str(result_pull.returncode)}') - def run_container(self, OpenSearch_image_id: str, OpenSearchDashboard_image_id: str, version: str) -> Any: + def run_container(self, image_ids: dict, version: str) -> Any: self.docker_compose_files = { '1': 'docker-compose-1.x.yml', '2': 'docker-compose-2.x.yml' @@ -250,10 +249,8 @@ def run_container(self, OpenSearch_image_id: str, OpenSearchDashboard_image_id: self.source_file = os.path.join('docker', 'release', 'dockercomposefiles', self.docker_compose_files[self.major_version_number]) shutil.copy2(self.source_file, self.target_yml_file) - self.replacements = [ - (f'opensearchproject/opensearch:{self.major_version_number}', f'{OpenSearch_image_id}'), - (f'opensearchproject/opensearch-dashboards:{self.major_version_number}', f'{OpenSearchDashboard_image_id}') - ] + 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)) # spin up containers diff --git a/src/validation_workflow/download_utils.py b/src/validation_workflow/download_utils.py index 0718f29e2b..9e7101fe7d 100644 --- a/src/validation_workflow/download_utils.py +++ b/src/validation_workflow/download_utils.py @@ -17,7 +17,7 @@ class DownloadUtils: @staticmethod def is_url_valid(url: str) -> bool: response = requests.head(url) - status = bool(response.status_code == 200) + status = bool(response.status_code in [200, 302]) return status @staticmethod diff --git a/src/validation_workflow/rpm/validation_rpm.py b/src/validation_workflow/rpm/validation_rpm.py index fa8cb4322f..6754ee3a0a 100644 --- a/src/validation_workflow/rpm/validation_rpm.py +++ b/src/validation_workflow/rpm/validation_rpm.py @@ -21,28 +21,33 @@ class ValidateRpm(Validation, DownloadUtils): def __init__(self, args: ValidationArgs) -> None: super().__init__(args) - self.base_url = "https://artifacts.opensearch.org/releases/bundle/" + 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: - architectures = ["x64", "arm64"] + isFilePathEmpty = bool(self.args.file_path) for project in self.args.projects: - for architecture in architectures: - url = f"{self.base_url}{project}/{self.args.version}/{project}-{self.args.version}-linux-{architecture}.rpm" - if ValidateRpm.is_url_valid(url) and ValidateRpm.download(url, self.tmp_dir): - logging.info(f"Valid URL - {url} and Download Successful !") + 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: - logging.info(f"Invalid URL - {url}") - raise Exception(f"Invalid url - {url}") + 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: execute('sudo rpm --import https://artifacts.opensearch.org/publickeys/opensearch.pgp', str(self.tmp_dir.path), True, False) for project in self.args.projects: + self.filename = os.path.basename(self.args.file_path.get(project)) execute(f'sudo yum remove {project} -y', ".") - path = os.path.join(self.tmp_dir.path, project + "-" + self.args.version + "-linux-" + self.args.arch + ".rpm") - execute(f'sudo rpm -ivh {path}', str(self.tmp_dir.path), True, False) + execute(f'sudo rpm -ivh {os.path.join(self.tmp_dir.path, self.filename)}', str(self.tmp_dir.path), True, False) except: raise Exception('Failed to install Opensearch') return True @@ -50,7 +55,6 @@ def installation(self) -> bool: def start_cluster(self) -> bool: try: for project in self.args.projects: - execute(f'sudo systemctl enable {project}', ".") execute(f'sudo systemctl start {project}', ".") time.sleep(20) (stdout, stderr, status) = execute(f'sudo systemctl status {project}', ".") @@ -64,7 +68,7 @@ def start_cluster(self) -> bool: return True def validation(self) -> bool: - test_result, counter = ApiTestCases().test_cases() + test_result, counter = ApiTestCases().test_apis(self.args.projects) if (test_result): logging.info(f'All tests Pass : {counter}') return True @@ -75,6 +79,7 @@ def cleanup(self) -> bool: try: for project in self.args.projects: execute(f'sudo systemctl stop {project}', ".") - except: - raise Exception('Failed to Stop Cluster') + execute(f'sudo yum remove {project} -y', ".") + 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/tar/validation_tar.py b/src/validation_workflow/tar/validation_tar.py index c5a81d5ceb..1666481e6c 100644 --- a/src/validation_workflow/tar/validation_tar.py +++ b/src/validation_workflow/tar/validation_tar.py @@ -22,45 +22,51 @@ class ValidateTar(Validation, DownloadUtils): def __init__(self, args: ValidationArgs) -> None: super().__init__(args) - self.base_url = "https://artifacts.opensearch.org/releases/bundle/" + 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: - architectures = ["x64", "arm64"] + isFilePathEmpty = bool(self.args.file_path) for project in self.args.projects: - for architecture in architectures: - url = f"{self.base_url}{project}/{self.args.version}/{project}-{self.args.version}-linux-{architecture}.tar.gz" - if ValidateTar.is_url_valid(url) and ValidateTar.download(url, self.tmp_dir): - logging.info(f"Valid URL - {url} and Download Successful !") + 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: - logging.info(f"Invalid URL - {url}") - raise Exception(f"Invalid url - {url}") + 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: - fileName = f"{project}-{self.args.version}-{self.args.platform}-{self.args.arch}" - execute('tar -zxf ' + os.path.join(str(self.tmp_dir.path), fileName) + '.tar.gz -C ' + str(self.tmp_dir.path), ".", True, False) + self.filename = os.path.basename(self.args.file_path.get(project)) + execute('mkdir ' + os.path.join(self.tmp_dir.path, project) + ' | tar -xzf ' + os.path.join(str(self.tmp_dir.path), self.filename) + ' -C ' + os.path.join(self.tmp_dir.path, project) + ' --strip-components=1', ".", True, False) # noqa: E501 except: - raise Exception('Failed to Install Opensearch') + raise Exception('Failed to install Opensearch') return True def start_cluster(self) -> bool: try: - self.os_process.start(os.path.join(self.tmp_dir.path, "opensearch-" + self.args.version, "opensearch-tar-install.sh"), ".") + self.os_process.start(os.path.join(self.tmp_dir.path, "opensearch", "opensearch-tar-install.sh"), ".") 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') - self.osd_process.start(os.path.join(str(self.tmp_dir.path), "opensearch-dashboards-" + self.args.version, "bin", "opensearch-dashboards"), ".") - time.sleep(20) except: raise Exception('Failed to Start Cluster') return True def validation(self) -> bool: - test_result, counter = ApiTestCases().test_cases() + test_result, counter = ApiTestCases().test_apis(self.args.projects) if (test_result): logging.info(f'All tests Pass : {counter}') else: @@ -70,7 +76,8 @@ def validation(self) -> bool: def cleanup(self) -> bool: try: self.os_process.terminate() - self.osd_process.terminate() + if ("opensearch-dashboards" in self.args.projects): + self.osd_process.terminate() except: - raise Exception('Failed to Stop Cluster') + raise Exception('Failed to terminate the processes that started OpenSearch and OpenSearch-Dashboards') return True diff --git a/src/validation_workflow/validation.py b/src/validation_workflow/validation.py index c24604cd6f..8b9e53c423 100644 --- a/src/validation_workflow/validation.py +++ b/src/validation_workflow/validation.py @@ -6,26 +6,43 @@ # compatible open source license. +import logging +import shutil from abc import ABC, abstractmethod from typing import Any +from validation_workflow.download_utils import DownloadUtils from validation_workflow.validation_args import ValidationArgs class Validation(ABC): """ - Abstract class for all types of artifact validation + Abstract class for all types of artifact validation """ def __init__(self, args: ValidationArgs) -> None: super().__init__() self.args = args + def check_url(self, url: str) -> bool: + if DownloadUtils().download(url, self.tmp_dir) and DownloadUtils().is_url_valid(url): # type: ignore + logging.info(f"Valid URL - {url} and Download Successful !") + return True + else: + raise Exception(f"Invalid url - {url}") + + def copy_artifact(self, filepath: str, tempdir_path: str) -> bool: + if filepath: + shutil.copy2(filepath, tempdir_path) + return True + else: + raise Exception("Provided path for local artifacts does not exist") + def run(self) -> Any: try: return self.download_artifacts() and self.installation() and self.start_cluster() and self.validation() and self.cleanup() - except Exception: - return False + except Exception as e: + raise Exception(f'An error occurred while running the validation tests: {str(e)}') @abstractmethod def download_artifacts(self) -> bool: diff --git a/src/validation_workflow/validation_args.py b/src/validation_workflow/validation_args.py index c8b30e5029..2b412124d2 100644 --- a/src/validation_workflow/validation_args.py +++ b/src/validation_workflow/validation_args.py @@ -9,6 +9,8 @@ import logging import textwrap +from test_workflow.test_kwargs import TestKwargs + class ValidationArgs: SUPPORTED_PLATFORMS = ["linux"] @@ -20,14 +22,24 @@ def __init__(self) -> None: formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent('''\ Example : ./validation.sh --version 2.3.0 --distribution rpm --platform linux - ./validation.sh --version 2.3.0 --distribution docker --os-build-number 6039 --osd-build-number 4104 ./validation.sh --version 2.3.0 --distribution docker --os-build-number 6039 --osd-build-number 4104 --using-staging-artifact-only + ./validation.sh --version 2.3.0 --projects opensearch opensearch-dashboards --artifact-type staging + ./validation.sh --file-path https://artifacts.opensearch.org/releases/bundle/opensearch/2.3.0/opensearch-2.3.0-linux-x64.tar.gz ''')) parser.add_argument( "--version", type=str, - required=True, - help="(manadatory) Product version to validate" + required=False, + help="(manadatory for production) Product version to validate", + default="" + ) + parser.add_argument( + "--file-path", + nargs='*', + action=TestKwargs, + help="(manadatory for production) Product URL or file-path to validate", + default={}, + dest="file_path" ) parser.add_argument( "-d", @@ -38,7 +50,6 @@ def __init__(self) -> None: dest="distribution" ) parser.add_argument( - "-p", "--platform", type=str, choices=self.SUPPORTED_PLATFORMS, @@ -50,7 +61,7 @@ def __init__(self) -> None: type=str, required=False, help="(optional) The opensearchstaging OpenSearch image build number if required, for example : 6039\n", - default="", + default="latest", dest="os_build_number", ) parser.add_argument( @@ -58,7 +69,7 @@ def __init__(self) -> None: type=str, required=False, help="(optional) The opensearchstaging OpenSearchDashboard image build number if required, for example : 4104\n", - default="", + default="latest", dest="osd_build_number", ) parser.add_argument( @@ -85,6 +96,21 @@ def __init__(self) -> None: choices=["x64", "arm64"], default="x64" ) + parser.add_argument( + "-p", + "--projects", + nargs='+', + help="Enter type of projects to be validated", + choices=["opensearch", "opensearch-dashboards"], + default=["opensearch"] + ) + parser.add_argument( + "--artifact-type", + help="Enter type of artifacts that needs to be validated", + choices=["staging", "production"], + default="production", + dest="artifact_type", + ) group = parser.add_mutually_exclusive_group() group.add_argument( "--validate-digest-only", @@ -98,20 +124,42 @@ def __init__(self) -> None: help="(optional) Use only staging artifact to run docker and API test, will not validate digest") 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): + 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") + self.version = args.version + self.file_path = args.file_path + self.artifact_type = args.artifact_type self.logging_level = args.logging_level self.distribution = args.distribution self.platform = args.platform - self.projects = ["opensearch", "opensearch-dashboards"] + 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 self.docker_source = args.docker_source self.validate_digest_only = args.validate_digest_only self.using_staging_artifact_only = args.using_staging_artifact_only + def get_distribution_type(self, file_path: dict) -> str: + if (any("tar" in value for value in file_path.values())): + return 'tar' + elif (any("rpm" in value for value in file_path.values())): + return 'rpm' + elif (any("repo" in value for value in file_path.values())): + return 'yum' + else: + raise Exception("Provided distribution is not supported") + def stg_tag(self, image_type: str) -> str: return " ".join( filter( diff --git a/src/validation_workflow/yum/validation_yum.py b/src/validation_workflow/yum/validation_yum.py index 1b1142c415..78ffcb9483 100644 --- a/src/validation_workflow/yum/validation_yum.py +++ b/src/validation_workflow/yum/validation_yum.py @@ -6,6 +6,8 @@ # compatible open source license. import logging +import os +import re import time from system.execute import execute @@ -20,17 +22,27 @@ class ValidateYum(Validation, DownloadUtils): def __init__(self, args: ValidationArgs) -> None: super().__init__(args) - self.base_url = "https://artifacts.opensearch.org/releases/bundle/" + 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: - url = f"{self.base_url}{project}/{self.args.version[0:1]}.x/{project}-{self.args.version[0:1]}.x.repo" - if ValidateYum.is_url_valid(url) and ValidateYum.download(url, self.tmp_dir): - logging.info(f"Valid URL - {url} and Download Successful !") + 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 = re.search(r'(\d+\.\d+\.\d+)', os.path.basename(self.args.file_path.get(project))).group(1) + self.check_url(self.args.file_path.get(project)) + else: - logging.info(f"Invalid URL - {url}") - raise Exception(f"Invalid url - {url}") + 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: @@ -39,7 +51,7 @@ def installation(self) -> bool: for project in self.args.projects: execute(f'sudo yum remove {project} -y', ".") logging.info('Removed previous versions of Opensearch') - urllink = f"{self.base_url}{project}/{self.args.version[0:1]}.x/{project}-{self.args.version[0:1]}.x.repo -o /etc/yum.repos.d/{project}-{self.args.version[0:1]}.x.repo" + urllink = f"{self.args.file_path.get(project)} -o /etc/yum.repos.d/{os.path.basename(self.args.file_path.get(project))}" execute(f'sudo curl -SL {urllink}', ".") execute(f"sudo yum install '{project}-{self.args.version}' -y", ".") except: @@ -49,7 +61,6 @@ def installation(self) -> bool: def start_cluster(self) -> bool: try: for project in self.args.projects: - execute(f'sudo systemctl enable {project}', ".") execute(f'sudo systemctl start {project}', ".") time.sleep(20) execute(f'sudo systemctl status {project}', ".") @@ -58,7 +69,7 @@ def start_cluster(self) -> bool: return True def validation(self) -> bool: - test_result, counter = ApiTestCases().test_cases() + test_result, counter = ApiTestCases().test_apis(self.args.projects) if (test_result): logging.info(f'All tests Pass : {counter}') return True @@ -69,6 +80,7 @@ def cleanup(self) -> bool: try: for project in self.args.projects: execute(f'sudo systemctl stop {project}', ".") - except: - raise Exception('Failed to Stop Cluster') + execute(f'sudo yum remove {project} -y', ".") + 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/tests/tests_validation_workflow/test_api_test_cases.py b/tests/tests_validation_workflow/test_api_test_cases.py index d3c36e01cb..f72b80dc5b 100644 --- a/tests/tests_validation_workflow/test_api_test_cases.py +++ b/tests/tests_validation_workflow/test_api_test_cases.py @@ -6,24 +6,29 @@ # compatible open source license. import unittest -from unittest.mock import Mock, call, patch +from unittest.mock import Mock, patch from validation_workflow.api_test_cases import ApiTestCases class TestTestCases(unittest.TestCase): - @patch('validation_workflow.api_test_cases.ValidationArgs') @patch('validation_workflow.api_test_cases.ApiTest.api_get') - def test_test_cases(self, mock_api_get: Mock, mock_validation_args: Mock) -> None: - mock_validation_args.return_value.stg_tag.return_value = '1.0.0.1000' + def test_opensearch(self, mock_api_get: Mock) -> None: mock_api_get.return_value = (200, 'green') testcases = ApiTestCases() - result = testcases.test_cases() + result = testcases.test_apis(['opensearch']) - self.assertEqual(result[1], 'There are 2/4 test cases Pass') + self.assertEqual(result[1], 'There are 3/3 test cases Pass') + self.assertEqual(mock_api_get.call_count, 3) + + @patch('validation_workflow.api_test_cases.ApiTest.api_get') + def test_both(self, mock_api_get: Mock) -> None: + mock_api_get.return_value = (200, 'green') + testcases = ApiTestCases() + result = testcases.test_apis(['opensearch', 'opensearch-dashboards']) + + self.assertEqual(result[1], 'There are 4/4 test cases Pass') self.assertEqual(mock_api_get.call_count, 4) - mock_validation_args.assert_has_calls([call(), call().stg_tag('opensearch'), call(), call().stg_tag('opensearch_dashboards')]) - mock_validation_args.return_value.stg_tag.assert_has_calls([call('opensearch'), call('opensearch_dashboards')]) if __name__ == '__main__': diff --git a/tests/tests_validation_workflow/test_download_utils.py b/tests/tests_validation_workflow/test_download_utils.py new file mode 100644 index 0000000000..dd9feaf70d --- /dev/null +++ b/tests/tests_validation_workflow/test_download_utils.py @@ -0,0 +1,52 @@ +# 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 Mock, patch + +from system.temporary_directory import TemporaryDirectory +from validation_workflow.download_utils import DownloadUtils + + +class TestDownloadUtils(unittest.TestCase): + @patch('requests.head') + def test_is_url_valid_true(self, mock_head: Mock) -> None: + mock_head.return_value.status_code = 302 + url = "https://opensearch.org/release/2.11.0/opensearch-2.11.0-linux-arm64.tar.gz" + result = DownloadUtils.is_url_valid(url) + self.assertTrue(result) + + mock_head.return_value.status_code = 200 + result = DownloadUtils.is_url_valid(url) + self.assertTrue(result) + + @patch('requests.head') + def test_is_url_valid_false(self, mock_head: Mock) -> None: + mock_head.return_value.status_code = 404 + + url = "https://opensearch.org/release/2.11.0/opensearch-2.11.0-linux-arm64.tar.gz" + result = DownloadUtils.is_url_valid(url) + + self.assertFalse(result) + + @patch('requests.get') + @patch('builtins.open', create=True) + @patch('os.mkdir', return_value=None) + def test_download(self, mock_mkdir: Mock, mock_open: Mock, mock_get: Mock) -> None: + mock_get.return_value.content = "exists" + mock_response = Mock() + mock_response.status_code = 200 + mock_get.return_value = mock_response + + tmp_dir = TemporaryDirectory() + + url = "https://opensearch.org/release/2.11.0/opensearch-2.11.0-linux-arm64.tar.gz" + result = DownloadUtils.download(url, tmp_dir) + + self.assertTrue(result) + mock_open.assert_called_once() + mock_get.assert_called_once() diff --git a/tests/tests_validation_workflow/test_validation.py b/tests/tests_validation_workflow/test_validation.py new file mode 100644 index 0000000000..acdff6df32 --- /dev/null +++ b/tests/tests_validation_workflow/test_validation.py @@ -0,0 +1,63 @@ +# 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 Mock, patch + +from system.temporary_directory import TemporaryDirectory +from validation_workflow.tar.validation_tar import ValidateTar +from validation_workflow.validation import Validation +from validation_workflow.validation_args import ValidationArgs + + +class ImplementValidation(Validation): + 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 + + def start_cluster(self) -> None: + return None + + def validation(self) -> None: + return None + + def cleanup(self) -> None: + return None + + +class TestValidation(unittest.TestCase): + + @patch('validation_workflow.download_utils.DownloadUtils') + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + def test_check_url_valid(self, mock_validation_args: Mock, mock_download_utils: Mock) -> None: + mock_validation_args.projects.return_value = ["opensearch"] + + mock_validation = ValidateTar(mock_validation_args.return_value) + mock_download_utils_download = mock_download_utils.return_value + mock_download_utils_download.download.return_value = True + mock_download_utils_download.is_url_valid.return_value = True + url = "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/1.3.12/latest/linux/x64/rpm/dist/opensearch/opensearch-1.3.12.staging.repo" + + result = mock_validation.check_url(url) + self.assertTrue(result) + + @patch('shutil.copy2', return_value=True) + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + def test_copy_artifact(self, mock_validation_args: Mock, mock_copy: Mock) -> None: + mock_validation_args.projects.return_value = ["opensearch"] + mock_validation = ValidateTar(mock_validation_args.return_value) + + url = "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/1.3.12/latest/linux/x64/rpm/dist/opensearch/opensearch-1.3.12.staging.repo" + + result = mock_validation.copy_artifact(url, "tmp/tthcdhfh/") + self.assertTrue(result) diff --git a/tests/tests_validation_workflow/test_validation_args.py b/tests/tests_validation_workflow/test_validation_args.py index 3cb4e6ce94..d0299db3f1 100644 --- a/tests/tests_validation_workflow/test_validation_args.py +++ b/tests/tests_validation_workflow/test_validation_args.py @@ -23,6 +23,13 @@ def test_version(self) -> None: self.assertEqual(ValidationArgs().version, "2.3.0") self.assertNotEqual(ValidationArgs().version, "2.1.0") + @patch("argparse._sys.argv", [VALIDATION_PY]) + def test_without_arguments(self) -> None: + with self.assertRaises(Exception) as ctx: + self.assertEqual(ValidationArgs().version, "") + self.assertEqual(ValidationArgs().file_path, "") + self.assertEqual(str(ctx.exception), "Provide either version number or File Path") + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "2.1.0", "--distribution", "rpm"]) def test_rpm_distribution(self) -> None: self.assertEqual(ValidationArgs().distribution, "rpm") @@ -51,3 +58,38 @@ def test_verbose_default(self) -> None: @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.0", "--verbose"]) def test_verbose_true(self) -> None: self.assertTrue(ValidationArgs().logging_level, logging.DEBUG) + + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.rpm"]) + def test_file_path(self) -> None: + self.assertNotEqual(ValidationArgs().file_path, "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.rpm") + + @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_artifact_type(self) -> None: + self.assertNotEqual(ValidationArgs().artifact_type, "production") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.0", "--projects", "opensearch"]) + def test_set_projects(self) -> None: + self.assertEqual(ValidationArgs().projects, ["opensearch"]) + + @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") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.zip"]) + def test_file_path_distribution_type(self) -> None: + with self.assertRaises(Exception) as ctx: + self.assertEqual(ValidationArgs().projects, ["opensearch"]) + self.assertEqual(str(ctx.exception), "Provided distribution is not supported") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.tar.gz"]) + def test_get_distribution_type_tar(self) -> None: + result = ValidationArgs().get_distribution_type(ValidationArgs().file_path) + self.assertEqual(result, "tar") + + @patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.x.staging.repo "]) + def test_get_distribution_type_yum(self) -> None: + result = ValidationArgs().get_distribution_type(ValidationArgs().file_path) + self.assertEqual(result, "yum") diff --git a/tests/tests_validation_workflow/test_validation_docker.py b/tests/tests_validation_workflow/test_validation_docker.py index 86939c6447..281cd7acb4 100644 --- a/tests/tests_validation_workflow/test_validation_docker.py +++ b/tests/tests_validation_workflow/test_validation_docker.py @@ -20,6 +20,7 @@ class TestValidateDocker(unittest.TestCase): @patch('validation_workflow.docker.validation_docker.ValidateDocker.is_container_daemon_running') def test_download_artifacts(self, mock_is_container_daemon_running: Mock, mock_validation_args: Mock, mock_get_image_id: Mock) -> None: mock_validation_args = Mock() + mock_validation_args.return_value.projects = ["opensearch"] mock_validation_args.return_value.docker_source = 'dockerhub' mock_validation_args.return_value.using_staging_artifact_only = True @@ -43,22 +44,23 @@ def test_download_artifacts(self, mock_is_container_daemon_running: Mock, mock_v @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_validation(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_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: # Set up mock objects mock_validation_args.return_value.OS_image = 'opensearchstaging/opensearch-os' - mock_validation_args.return_value.OSD_image = 'opensearchstaging/opensearch-osd' mock_validation_args.return_value.version = '1.0.0.1000' mock_validation_args.return_value.validate_digest_only = False + mock_validation_args.return_value.projects = ["opensearch"] mock_docker_image.return_value = MagicMock() mock_container.return_value = (True, 'test_file.yml') - mock_test_cases_instance = mock_test.return_value - mock_test_cases_instance.test_cases.return_value = (True, 2) + mock_test_apis_instance = mock_test.return_value + mock_test_apis_instance.test_apis.return_value = (True, 2) mock_digest.return_value = True mock_check_http.return_value = True # Create instance of ValidateDocker class validate_docker = ValidateDocker(mock_validation_args.return_value) - validate_docker.image_ids = ['images_id_0', 'images_id_0'] + validate_docker.image_ids = {'opensearch': 'images_id_0'} + validate_docker.replacements = [('opensearchproject/opensearch:1', 'images_id_0')] # Call validation method and assert the result result = validate_docker.validation() @@ -67,7 +69,40 @@ def test_validation(self, mock_time_sleep: Mock, mock_digest: Mock, mock_contain # Assert that the mock methods are called as expected mock_container.assert_called_once() mock_test.assert_called_once() - mock_test.assert_has_calls([call(), call().test_cases()]) + mock_test.assert_has_calls([call(), call().test_apis(['opensearch'])]) + + @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: + # 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 + mock_validation_args.return_value.projects = ["opensearch"] + mock_docker_image.return_value = MagicMock() + mock_container.return_value = (True, 'test_file.yml') + mock_test_apis_instance = mock_test.return_value + mock_test_apis_instance.test_apis.return_value = (True, 2) + mock_digest.return_value = True + mock_check_http.return_value = True + + # Create instance of ValidateDocker class + validate_docker = ValidateDocker(mock_validation_args.return_value) + + validate_docker.image_names_list = ['opensearchproject/opensearch'] + validate_docker.image_ids = {'opensearch': 'images_id_0'} + validate_docker.image_digests = [True] + validate_docker.replacements = [('opensearchproject/opensearch:1', 'images_id_0')] + + # Call validation method and assert the result + result = validate_docker.validation() + self.assertTrue(result) @patch('validation_workflow.docker.validation_docker.ValidationArgs') def test_cleanup(self, mock_validation_args: Mock) -> None: diff --git a/tests/tests_validation_workflow/test_validation_rpm.py b/tests/tests_validation_workflow/test_validation_rpm.py index 14259be835..d37b3f7de6 100644 --- a/tests/tests_validation_workflow/test_validation_rpm.py +++ b/tests/tests_validation_workflow/test_validation_rpm.py @@ -12,39 +12,88 @@ class TestValidationRpm(unittest.TestCase): + def setUp(self) -> None: + self.args = Mock() + self.call_methods = ValidateRpm(self.args) - @patch("validation_workflow.download_utils.DownloadUtils.is_url_valid", return_value=True) - @patch("validation_workflow.download_utils.DownloadUtils.download", return_value=True) - @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') - def test_download_artifacts(self, mock_validation_args: Mock, mock_is_url_valid: Mock, mock_download: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + def test_empty_file_path_and_production_artifact_type(self) -> None: + self.args.projects = ["opensearch"] + self.args.version = "2.4.0" + self.args.file_path = {} + self.args.artifact_type = "production" - validate_rpm = ValidateRpm(mock_validation_args) + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() - result = validate_rpm.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_once() - self.assertEqual(result, True) + def test_with_file_path_both_artifact_types(self) -> None: + self.args.projects = ["opensearch"] + self.args.file_path = {"opensearch": "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/1.3.12/latest/linux/x64/rpm/dist/opensearch/opensearch-1.3.11.rpm"} - @patch("validation_workflow.download_utils.DownloadUtils.is_url_valid", return_value=False) - @patch("validation_workflow.download_utils.DownloadUtils.download", return_value=False) - @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') - def test_download_artifacts_error(self, mock_validation_args: Mock, mock_is_url_valid: Mock, mock_download: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.11.0' + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_with(self.args.file_path["opensearch"]) - validate_rpm = ValidateRpm(mock_validation_args) + @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') + 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.artifact_type = "staging" + self.args.file_path = {} + self.args.build_number = {"opensearch": "1.2.3", "opensearch-dashboards": "1.2.3"} + + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_with(self.args.file_path["opensearch"]) + + @patch('shutil.copy2', return_value=True) + def test_local_artifacts(self, mock_copy: Mock) -> None: + self.args.file_path = {"opensearch": "opensearch.1.3.12.rpm"} + self.args.projects = ["opensearch"] + self.args.version = "" + self.args.arch = "arm64" + self.args.file_path = {"opensearch": "src/opensearch/opensearch-1.3.12.rpm"} + + with patch.object(self.call_methods, 'copy_artifact') as mock_copy_artifact: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_copy_artifact.assert_called_once() - self.assertRaises(Exception, validate_rpm.download_artifacts()) + @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') + def test_exceptions(self, mock_validation_args: Mock) -> None: + with self.assertRaises(Exception) as e1: + mock_validation_args.return_value.projects = ["opensearch"] + mock_validation_args.return_value.file_path = {"opensearch": "/src/files/opensearch.rpm"} + validate_rpm = ValidateRpm(mock_validation_args.return_value) + validate_rpm.installation() + self.assertEqual(str(e1.exception), "Failed to install Opensearch") + + with self.assertRaises(Exception) as e2: + mock_validation_args.return_value.projects = ["opensearch"] + validate_rpm = ValidateRpm(mock_validation_args.return_value) + validate_rpm.start_cluster() + self.assertEqual(str(e2.exception), "Failed to Start Cluster") + + with self.assertRaises(Exception) as e3: + 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 - @patch("validation_workflow.rpm.validation_rpm.execute", return_value=True) @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') - def test_installation(self, mock_validation_args: Mock, mock_execute: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.arch.return_value = 'x64' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + @patch("validation_workflow.rpm.validation_rpm.execute") + def test_installation(self, mock_system: 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.platform = 'linux' + mock_validation_args.return_value.projects = ["opensearch"] validate_rpm = ValidateRpm(mock_validation_args.return_value) - + mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") result = validate_rpm.installation() self.assertTrue(result) @@ -61,11 +110,11 @@ 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_cases: Mock, mock_validation_args: Mock) -> None: + def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: # Set up mock objects mock_validation_args.return_value.version = '2.3.0' - mock_test_cases_instance = mock_test_cases.return_value - mock_test_cases_instance.test_cases.return_value = (True, 4) + mock_test_apis_instance = mock_test_apis.return_value + mock_test_apis_instance.test_apis.return_value = (True, 4) # Create instance of ValidateRpm class validate_rpm = ValidateRpm(mock_validation_args.return_value) @@ -75,13 +124,31 @@ def test_validation(self, mock_test_cases: Mock, mock_validation_args: Mock) -> self.assertTrue(result) # Assert that the mock methods are called as expected - mock_test_cases.assert_called_once() + mock_test_apis.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 + 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, 1) + + # Create instance of ValidateRpm class + validate_rpm = ValidateRpm(mock_validation_args.return_value) + + # Call validation method and assert the result + validate_rpm.validation() + self.assertRaises(Exception, "Not all tests Pass : 1") + + # Assert that the mock methods are called as expected + mock_test_apis.assert_called_once() @patch("validation_workflow.rpm.validation_rpm.execute", return_value=True) @patch('validation_workflow.rpm.validation_rpm.ValidationArgs') def test_cleanup(self, mock_validation_args: Mock, mock_execute: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + mock_validation_args.return_value.version = '2.3.0' + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] # Create instance of ValidateRpm class validate_rpm = ValidateRpm(mock_validation_args.return_value) diff --git a/tests/tests_validation_workflow/test_validation_tar.py b/tests/tests_validation_workflow/test_validation_tar.py index cc996164d8..07a91628b9 100644 --- a/tests/tests_validation_workflow/test_validation_tar.py +++ b/tests/tests_validation_workflow/test_validation_tar.py @@ -6,46 +6,76 @@ # compatible open source license. import unittest -from unittest.mock import Mock, patch +from unittest.mock import MagicMock, Mock, patch from system.process import Process from validation_workflow.tar.validation_tar import ValidateTar -class TestValidationTar(unittest.TestCase): +class TestValidateTar(unittest.TestCase): + def setUp(self) -> None: + self.args = Mock() + self.call_methods = ValidateTar(self.args) - @patch("validation_workflow.download_utils.DownloadUtils.is_url_valid", return_value=True) - @patch("validation_workflow.download_utils.DownloadUtils.download", return_value=True) - @patch('validation_workflow.tar.validation_tar.ValidationArgs') - def test_download_artifacts(self, mock_validation_args: Mock, mock_is_url_valid: Mock, mock_download: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] - - validate_tar = ValidateTar(mock_validation_args) + def test_empty_file_path_and_production_artifact_type(self) -> None: + self.args.projects = ["opensearch"] + self.args.version = "2.4.0" + self.args.file_path = {} + self.args.artifact_type = "production" - result = validate_tar.download_artifacts() + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() - self.assertEqual(result, True) + self.assertTrue(result) + mock_check_url.assert_called_once() - @patch("validation_workflow.download_utils.DownloadUtils.is_url_valid", return_value=False) - @patch("validation_workflow.download_utils.DownloadUtils.download", return_value=False) - @patch('validation_workflow.tar.validation_tar.ValidationArgs') - def test_download_artifacts_error(self, mock_validation_args: Mock, mock_is_url_valid: Mock, mock_download: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.11.0' + def test_with_file_path_both_artifact_types(self) -> None: + self.args.projects = ["opensearch"] + self.args.file_path = {"opensearch": "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/1.3.12/latest/linux/x64/tar/dist/opensearch/opensearch-1.3.12.tar.gz"} - validate_tar = ValidateTar(mock_validation_args) + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_with(self.args.file_path["opensearch"]) - self.assertRaises(Exception, validate_tar.download_artifacts()) + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + 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.artifact_type = "staging" + self.args.file_path = {} + self.args.build_number = {"opensearch": "latest", "opensearch-dashboards": "latest"} + + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_with(self.args.file_path["opensearch"]) + + @patch('shutil.copy2', return_value=True) + def test_local_artifacts(self, mock_copy: Mock) -> None: + self.args.file_path = {"opensearch": "tar.gz"} + self.args.projects = ["opensearch"] + self.args.version = "" + self.args.arch = "x64" + self.args.file_path = {"opensearch": "src/opensearch/opensearch-1.3.12.tar.gz"} + + with patch.object(self.call_methods, 'copy_artifact') as mock_copy_artifact: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_copy_artifact.assert_called_once() - @patch("validation_workflow.tar.validation_tar.execute", return_value=True) @patch('validation_workflow.tar.validation_tar.ValidationArgs') - def test_installation(self, mock_validation_args: Mock, mock_system: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.arch.return_value = 'x64' - mock_validation_args.return_value.platform.return_value = 'linux' + @patch('os.path.basename') + @patch("validation_workflow.tar.validation_tar.execute") + def test_installation(self, mock_system: Mock, mock_basename: 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.platform = 'linux' + mock_validation_args.return_value.projects = ["opensearch"] validate_tar = ValidateTar(mock_validation_args.return_value) - + mock_basename.side_effect = lambda path: "mocked_filename" + mock_system.side_effect = lambda *args, **kwargs: (0, "stdout_output", "stderr_output") result = validate_tar.installation() self.assertTrue(result) @@ -54,28 +84,75 @@ def test_installation(self, mock_validation_args: Mock, mock_system: Mock) -> No @patch('time.sleep') def test_start_cluster(self, mock_sleep: 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.platforme = 'linux' + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] + validate_tar = ValidateTar(mock_validation_args.return_value) result = validate_tar.start_cluster() self.assertTrue(result) + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + @patch('time.sleep') + def test_start_cluster_exception_os(self, mock_sleep: Mock, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.projects = ["opensearch"] + + validate_tar = ValidateTar(mock_validation_args.return_value) + validate_tar.os_process.start = MagicMock(side_effect=Exception('Failed to Start Cluster')) # type: ignore + with self.assertRaises(Exception) as context: + validate_tar.start_cluster() + + self.assertEqual(str(context.exception), 'Failed to Start Cluster') + @patch('validation_workflow.tar.validation_tar.ValidationArgs') @patch('validation_workflow.tar.validation_tar.ApiTestCases') - def test_validation(self, mock_test_cases: Mock, mock_validation_args: Mock) -> None: + def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: mock_validation_args.return_value.version = '2.3.0' - mock_test_cases_instance = mock_test_cases.return_value - mock_test_cases_instance.test_cases.return_value = (True, 4) + mock_test_apis_instance = mock_test_apis.return_value + 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_test_cases.assert_called_once() + mock_test_apis.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 + 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, 1) + + # Create instance of ValidateTar class + validate_tar = ValidateTar(mock_validation_args.return_value) + + # Call validation method and assert the result + validate_tar.validation() + self.assertRaises(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') @patch.object(Process, 'terminate') def test_cleanup(self, mock_terminate: Mock, mock_validation_args: Mock) -> None: - validation_args = mock_validation_args.return_value - validate_tar = ValidateTar(validation_args) + 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.projects = ["opensearch", "opensearch-dashboards"] + + validate_tar = ValidateTar(mock_validation_args.return_value) result = validate_tar.cleanup() self.assertTrue(result) + + @patch('validation_workflow.tar.validation_tar.ValidationArgs') + def test_cleanup_exception(self, mock_validation_args: Mock) -> None: + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] + validate_tar = ValidateTar(mock_validation_args.return_value) + with self.assertRaises(Exception) as context: + validate_tar.cleanup() + + self.assertEqual(str(context.exception), 'Failed to terminate the processes that started OpenSearch and OpenSearch-Dashboards') diff --git a/tests/tests_validation_workflow/test_validation_yum.py b/tests/tests_validation_workflow/test_validation_yum.py index ca25a6f774..421f250a6c 100644 --- a/tests/tests_validation_workflow/test_validation_yum.py +++ b/tests/tests_validation_workflow/test_validation_yum.py @@ -13,35 +13,84 @@ class TestValidationYum(unittest.TestCase): - @patch("validation_workflow.download_utils.DownloadUtils.is_url_valid", return_value=True) - @patch("validation_workflow.download_utils.DownloadUtils.download", return_value=True) - @patch('validation_workflow.yum.validation_yum.ValidationArgs') - def test_download_artifacts(self, mock_validation_args: Mock, mock_is_url_valid: Mock, mock_download: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + def setUp(self) -> None: + self.args = Mock() + self.call_methods = ValidateYum(self.args) - validate_yum = ValidateYum(mock_validation_args) + def test_empty_file_path_and_production_artifact_type(self) -> None: + self.args.projects = ["opensearch"] + self.args.version = "2.5.0" + self.args.file_path = {} + self.args.artifact_type = "production" - result = validate_yum.download_artifacts() + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() - self.assertEqual(result, True) + self.assertTrue(result) + mock_check_url.assert_called_once() - @patch("validation_workflow.download_utils.DownloadUtils.is_url_valid", return_value=False) - @patch("validation_workflow.download_utils.DownloadUtils.download", return_value=False) - @patch('validation_workflow.yum.validation_yum.ValidationArgs') - def test_download_artifacts_error(self, mock_validation_args: Mock, mock_is_url_valid: Mock, mock_download: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.11.0' + def test_with_file_path_both_artifact_types(self) -> None: + self.args.projects = ["opensearch"] + self.args.file_path = {"opensearch": "https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/1.3.12/latest/linux/x64/yum/dist/opensearch/opensearch-1.3.11.staging.repo"} - validate_yum = ValidateYum(mock_validation_args) + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_with(self.args.file_path["opensearch"]) - self.assertRaises(Exception, validate_yum.download_artifacts()) + @patch('validation_workflow.yum.validation_yum.ValidationArgs') + 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.artifact_type = "staging" + self.args.file_path = {} + self.args.build_number = {"opensearch": "1.2.3", "opensearch-dashboards": "1.2.3"} + + with patch.object(self.call_methods, 'check_url') as mock_check_url: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_check_url.assert_called_with(self.args.file_path["opensearch"]) + + @patch('shutil.copy2', return_value=True) + def test_local_artifacts(self, mock_copy: Mock) -> None: + self.args.file_path = {"opensearch": "opensearch.1.3.12.staging.repo"} + self.args.projects = ["opensearch"] + self.args.version = "" + self.args.arch = "arm64" + self.args.file_path = {"opensearch": "src/opensearch/opensearch-1.3.12.staging.repo"} + + with patch.object(self.call_methods, 'copy_artifact') as mock_copy_artifact: + result = self.call_methods.download_artifacts() + self.assertTrue(result) + mock_copy_artifact.assert_called_once() + + @patch('validation_workflow.yum.validation_yum.ValidationArgs') + def test_exceptions(self, mock_validation_args: Mock) -> None: + with self.assertRaises(Exception) as e1: + mock_validation_args.return_value.projects = ["opensearch"] + mock_validation_args.return_value.file_path = {"opensearch": "/src/files/opensearch.staging.repo"} + validate_yum = ValidateYum(mock_validation_args.return_value) + validate_yum.installation() + self.assertEqual(str(e1.exception), "Failed to install Opensearch") + + with self.assertRaises(Exception) as e2: + mock_validation_args.return_value.projects = ["opensearch"] + validate_yum = ValidateYum(mock_validation_args.return_value) + validate_yum.start_cluster() + self.assertEqual(str(e2.exception), "Failed to Start Cluster") + + with self.assertRaises(Exception) as e3: + 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 @patch("validation_workflow.yum.validation_yum.execute", return_value=True) @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.return_value = '2.3.0' - mock_validation_args.return_value.arch.return_value = 'x64' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + mock_validation_args.return_value.version = '2.3.0' + mock_validation_args.return_value.arch = 'x64' + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] validate_yum = ValidateYum(mock_validation_args.return_value) @@ -52,7 +101,7 @@ def test_installation(self, mock_validation_args: Mock, mock_execute: Mock) -> N @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: - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] validate_yum = ValidateYum(mock_validation_args.return_value) @@ -61,11 +110,11 @@ 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_cases: Mock, mock_validation_args: Mock) -> None: + def test_validation(self, mock_test_apis: Mock, mock_validation_args: Mock) -> None: # Set up mock objects mock_validation_args.return_value.version = '2.3.0' - mock_test_cases_instance = mock_test_cases.return_value - mock_test_cases_instance.test_cases.return_value = (True, 4) + mock_test_apis_instance = mock_test_apis.return_value + mock_test_apis_instance.test_apis.return_value = (True, 4) # Create instance of ValidateYum class validate_yum = ValidateYum(mock_validation_args.return_value) @@ -75,13 +124,31 @@ def test_validation(self, mock_test_cases: Mock, mock_validation_args: Mock) -> self.assertTrue(result) # Assert that the mock methods are called as expected - mock_test_cases.assert_called_once() + mock_test_apis.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: + # Set up mock objects + 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, 1) + + # Create instance of ValidateRpm class + validate_yum = ValidateYum(mock_validation_args.return_value) + + # Call validation method and assert the result + validate_yum.validation() + self.assertRaises(Exception, "Not all tests Pass : 1") + + # Assert that the mock methods are called as expected + mock_test_apis.assert_called_once() @patch("validation_workflow.yum.validation_yum.execute", return_value=True) @patch('validation_workflow.yum.validation_yum.ValidationArgs') def test_cleanup(self, mock_validation_args: Mock, mock_execute: Mock) -> None: - mock_validation_args.return_value.version.return_value = '2.3.0' - mock_validation_args.return_value.projects.return_value = ["opensearch", "opensearch-dashboards"] + mock_validation_args.return_value.version = '2.3.0' + mock_validation_args.return_value.projects = ["opensearch", "opensearch-dashboards"] validate_yum = ValidateYum(mock_validation_args.return_value)