Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BWC test workflow #1603

Merged
merged 2 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions manifests/2.0.0/opensearch-dashboards-2.0.0-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: OpenSearch Dashboards
components:
- name: OpenSearch-Dashboards
bwc-test:
test-configs:
- without-security
- name: functionalTestDashboards
integ-test:
test-configs:
- with-security
- without-security
schema-version: '1.0'
Copy link
Member

Choose a reason for hiding this comment

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

Nit; Can we move this to the top?

20 changes: 13 additions & 7 deletions src/run_bwc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os
import sys

from manifests.bundle_manifest import BundleManifest
from manifests.test_manifest import TestManifest
from system import console
from system.temporary_directory import TemporaryDirectory
from test_workflow.bwc_test.bwc_test_suite import BwcTestSuite
from test_workflow.bwc_test.bwc_test_runners import BwcTestRunners
from test_workflow.test_args import TestArgs


def main():
args = TestArgs()

# Any logging.info call preceding to next line in the execution chain will make the console output not displaying logs in console.
console.configure(level=args.logging_level)
with TemporaryDirectory(keep=args.keep) as work_dir:
bundle_manifest = BundleManifest.from_urlpath(args.paths.get("opensearch", os.getcwd()))
BwcTestSuite(bundle_manifest, work_dir.name, args.component, args.keep).execute()

test_manifest = TestManifest.from_path(args.test_manifest_path)

all_results = BwcTestRunners.from_test_manifest(args, test_manifest).run()

all_results.log()

if all_results.failed():
sys.exit(1)


if __name__ == "__main__":
Expand Down
40 changes: 40 additions & 0 deletions src/test_workflow/bwc_test/bwc_test_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 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 abc
import logging
import os

from system.temporary_directory import TemporaryDirectory
from test_workflow.test_recorder.test_recorder import TestRecorder
from test_workflow.test_result.test_suite_results import TestSuiteResults


class BwcTestRunner(abc.ABC):
def __init__(self, args, test_manifest):
self.args = args
self.test_manifest = test_manifest

self.tests_dir = os.path.join(os.getcwd(), "test-results")
os.makedirs(self.tests_dir, exist_ok=True)
self.test_recorder = TestRecorder(self.args.test_run_id, "bwc-test", self.tests_dir)

def run(self):
with TemporaryDirectory(keep=self.args.keep, chdir=True) as work_dir:
all_results = TestSuiteResults()
for component in self.components.select(focus=self.args.component):
if component.name in self.test_manifest.components:
Copy link
Member

Choose a reason for hiding this comment

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

Not for this PR, but we do a similar workflow in many places and I don't love the double-select here, first pass --component name to pick a component, but then we also only want components that have a test configuration.

One option would be to fail if self.args.component not in self.test_manifest.components with an explicit error, and refactor components.select to take an array of components, i.e. self.components.select(focus=self.test_manifest.components).

Another, possibly better, could be to do self.components.select(focus=self.args.component, configs=self.test_manifest.components, key: "bwc_test") that would return a set of tuples of component + configuration. The code then becomes quite clean:

for component, config in self.components.select(focus=self.args.component, configs: self.test_manifest.components, key: "bwc_test"):
   test_suite = ...
   ...

Copy link
Member Author

Choose a reason for hiding this comment

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

Created a cleanup issue here: #1604 and assigned myself.

test_config = self.test_manifest.components[component.name]
if test_config.bwc_test:
test_suite = self.__create_test_suite__(component, test_config, work_dir)
test_results = test_suite.execute_tests()
all_results.append(component.name, test_results)
else:
logging.info(f"Skipping bwc-tests for {component.name}, as it is currently not supported")
else:
logging.info(f"Skipping bwc-tests for {component.name}, as it is currently not declared in the test manifest")

return all_results
34 changes: 34 additions & 0 deletions src/test_workflow/bwc_test/bwc_test_runner_opensearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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 manifests.test_manifest import TestManifest
from test_workflow.bwc_test.bwc_test_runner import BwcTestRunner
from test_workflow.bwc_test.bwc_test_start_properties_opensearch import BwcTestStartPropertiesOpenSearch
from test_workflow.bwc_test.bwc_test_suite_opensearch import BwcTestSuiteOpenSearch
from test_workflow.test_args import TestArgs


class BwcTestRunnerOpenSearch(BwcTestRunner):

def __init__(self, args: TestArgs, test_manifest: TestManifest):
super().__init__(args, test_manifest)
self.properties = BwcTestStartPropertiesOpenSearch(args.paths.get("opensearch", os.getcwd()))

self.components = self.properties.build_manifest.components

logging.info("Entering BWC test for OpenSearch")

def __create_test_suite__(self, component, test_config, work_dir):
return BwcTestSuiteOpenSearch(
work_dir.name,
component,
test_config,
self.test_recorder,
self.properties.bundle_manifest,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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 manifests.test_manifest import TestManifest
from test_workflow.bwc_test.bwc_test_runner import BwcTestRunner
from test_workflow.bwc_test.bwc_test_start_properties_opensearch_dashboards import BwcTestStartPropertiesOpenSearchDashboards
from test_workflow.bwc_test.bwc_test_suite_opensearch_dashboards import BwcTestSuiteOpenSearchDashboards
from test_workflow.test_args import TestArgs


class BwcTestRunnerOpenSearchDashboards(BwcTestRunner):

def __init__(self, args: TestArgs, test_manifest: TestManifest):
super().__init__(args, test_manifest)
self.properties = BwcTestStartPropertiesOpenSearchDashboards(args.paths.get("opensearch-dashboards", os.getcwd()))

self.components = self.properties.build_manifest.components
logging.info("Entering BWC test for OpenSearch Dashboards")

def __create_test_suite__(self, component, test_config, work_dir):
return BwcTestSuiteOpenSearchDashboards(
work_dir.name,
component,
test_config,
self.test_recorder,
self.properties.bundle_manifest,
)
22 changes: 22 additions & 0 deletions src/test_workflow/bwc_test/bwc_test_runners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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.


from manifests.test_manifest import TestManifest
from test_workflow.bwc_test.bwc_test_runner_opensearch import BwcTestRunnerOpenSearch
from test_workflow.bwc_test.bwc_test_runner_opensearch_dashboards import BwcTestRunnerOpenSearchDashboards
from test_workflow.test_args import TestArgs


class BwcTestRunners:
RUNNERS = {
"OpenSearch": BwcTestRunnerOpenSearch,
"OpenSearch Dashboards": BwcTestRunnerOpenSearchDashboards
}

@classmethod
def from_test_manifest(cls, args: TestArgs, test_manifest: TestManifest):
return cls.RUNNERS[test_manifest.name](args, test_manifest)
21 changes: 21 additions & 0 deletions src/test_workflow/bwc_test/bwc_test_start_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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 abc

from manifests.build_manifest import BuildManifest
from manifests.bundle_manifest import BundleManifest


class BwcTestStartProperties(abc.ABC):
def __init__(self, path, build_dir, bundle_dir):
self.path = path
self.build_dir = build_dir
self.bundle_dir = bundle_dir

self.bundle_manifest = BundleManifest.from_urlpath("/".join([self.path.rstrip("/"), self.bundle_dir]))
self.build_manifest = BuildManifest.from_urlpath("/".join([self.path.rstrip("/"), self.build_dir]))
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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.

from test_workflow.bwc_test.bwc_test_start_properties import BwcTestStartProperties


class BwcTestStartPropertiesOpenSearch(BwcTestStartProperties):
def __init__(self, path):
super().__init__(path, "builds/opensearch/manifest.yml", "dist/opensearch/manifest.yml")
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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.

from test_workflow.bwc_test.bwc_test_start_properties import BwcTestStartProperties


class BwcTestStartPropertiesOpenSearchDashboards(BwcTestStartProperties):
def __init__(self, path):
super().__init__(path, "builds/opensearch-dashboards/manifest.yml", "dist/opensearch-dashboards/manifest.yml")
114 changes: 84 additions & 30 deletions src/test_workflow/bwc_test/bwc_test_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,97 @@
# Modifications Copyright OpenSearch Contributors. See
# GitHub history for details.

import abc
import logging
import os

from git.git_repository import GitRepository
from paths.script_finder import ScriptFinder
from system.execute import execute
from test_workflow.test_component import TestComponent
from test_workflow.test_recorder.test_result_data import TestResultData
from test_workflow.test_result.test_component_results import TestComponentResults
from test_workflow.test_result.test_result import TestResult


class BwcTestSuite:
manifest: str
work_dir: str
component: str
keep: bool
class BwcTestSuite(abc.ABC):

def __init__(self, manifest, work_dir, component=None, keep=False):
self.manifest = manifest
def __init__(
self,
work_dir,
component,
test_config,
test_recorder,
manifest
):
self.work_dir = work_dir
self.component = component
self.keep = keep

def run_tests(self, work_dir, component_name):
script = ScriptFinder.find_bwc_test_script(component_name, work_dir)
(status, stdout, stderr) = execute(script, work_dir, True, False)
return (status, stdout, stderr)

def component_bwc_tests(self, component):
test_component = TestComponent(component.repository, component.commit_id)
test_component.checkout(os.path.join(self.work_dir, component.name))
try:
console_output = self.run_tests(os.path.join(self.work_dir, component.name), component.name)
return console_output
except:
# TODO: Store and report test failures for {component}
logging.info(f"Exception while running BWC tests for {component.name}")

def execute(self):
# For each component, check out the git repo and run `bwctest.sh`
for component in self.manifest.components.select(focus=self.component):
# TODO: Store and report test results, send notification via {console_output}
self.component_bwc_tests(component)
self.test_config = test_config
self.test_recorder = test_recorder
self.manifest = manifest

self.repo = GitRepository(
self.component.repository,
self.component.commit_id,
os.path.join(self.work_dir, self.component.name),
test_config.working_directory
)

self.save_logs = test_recorder.test_results_logs

def execute_tests(self):
test_results = TestComponentResults()

for config in self.test_config.bwc_test["test-configs"]:
status = self.execute_bwctest_sh(config)

test_results.append(TestResult(self.component.name, config, status))
return test_results

def execute_bwctest_sh(self, config):
security = self.is_security_enabled(config)
script = ScriptFinder.find_bwc_test_script(self.component.name, self.repo.working_directory)
if os.path.exists(script):
cmd = self.get_cmd(script, security, self.manifest.build.location)
self.repo_work_dir = os.path.join(
self.repo.dir, self.test_config.working_directory) if self.test_config.working_directory is not None else self.repo.dir
(status, stdout, stderr) = execute(cmd, self.repo_work_dir, True, False)

test_result_data = TestResultData(
self.component.name,
self.test_config,
status,
stdout,
stderr,
self.test_artifact_files
)
self.save_logs.save_test_result_data(test_result_data)
if stderr:
logging.info("BWC test run failed for component " + self.component.name)
logging.info(stderr)
return status
else:
logging.info(f"{script} does not exist. Skipping integ tests for {self.component.name}")

def is_security_enabled(self, config):
if config in ["with-security", "without-security"]:
return True if config == "with-security" else False
else:
raise InvalidTestConfigError("Unsupported test config: " + config)

def pretty_print_message(self, message):
logging.info("===============================================")
logging.info(message)
logging.info("===============================================")

@abc.abstractmethod
def get_cmd(self):
pass

@property
@abc.abstractmethod
def test_artifact_files(self):
pass


class InvalidTestConfigError(Exception):
pass
38 changes: 38 additions & 0 deletions src/test_workflow/bwc_test/bwc_test_suite_opensearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: Apache-2.0
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this class is a whole lot of stuff for effectively two lines of differentiated behavior. I don't think there is an easier way but food for thought

#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os

from test_workflow.bwc_test.bwc_test_suite import BwcTestSuite


class BwcTestSuiteOpenSearch(BwcTestSuite):

def __init__(
self,
work_dir,
component,
test_config,
test_recorder,
manifest
):

super().__init__(
work_dir,
component,
test_config,
test_recorder,
manifest
)

def get_cmd(self, script, security, manifest_build_location):
return f"{script}"
Copy link
Member Author

Choose a reason for hiding this comment

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

This should be updated but current bwctest.sh doesn't accept other arguments: https://github.com/opensearch-project/anomaly-detection/blob/main/bwctest.sh

kavilla marked this conversation as resolved.
Show resolved Hide resolved

@property
def test_artifact_files(self):
return {
"opensearch-bwc-test": os.path.join(self.repo_work_dir, "build", "reports", "tests", "bwcTest")
}
Loading