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

Add rpm support for integTest framework #1992

Closed
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 10 additions & 2 deletions src/system/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@


class Process:
def __init__(self) -> None:
def __init__(self, filename: str, distribution: str) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

Process is a generic class in system, it should have nothing to do with distribution, this doesn't belong here.

self.process: subprocess.Popen[bytes] = None
self.stdout: Any = None
self.stderr: Any = None
self.__stdout_data__: str = None
self.__stderr_data__: str = None
self.filename = filename
self.distribution = distribution

def start(self, command: str, cwd: str) -> None:
if self.started:
Expand All @@ -38,7 +40,7 @@ def terminate(self) -> int:
if not self.started:
raise ProcessNotStartedError()

parent = psutil.Process(self.process.pid)
parent = psutil.Process(self.pid)
logging.debug("Checking for child processes")
child_processes = parent.children(recursive=True)
for child in child_processes:
Expand All @@ -65,6 +67,10 @@ def terminate(self) -> int:
self.return_code = self.process.returncode
self.process = None

if self.distribution == "rpm":
Copy link
Member

Choose a reason for hiding this comment

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

This should not live in Process.

logging.info("Clean up process related files for packages")
subprocess.check_call(f"yum remove -y '{self.filename}*'", shell=True)

return self.return_code

@property
Expand All @@ -73,6 +79,8 @@ def started(self) -> bool:

@property
def pid(self) -> int:
if self.distribution == "rpm":
return int(subprocess.check_output(f"sleep 1 && systemctl show --property MainPID {self.filename}", encoding='UTF-8', shell=True).split("=")[1]) if self.started else None
return self.process.pid if self.started else None

@property
Expand Down
8 changes: 8 additions & 0 deletions src/test_workflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ To run OpenSearch Dashboards integration tests.
opensearch-dashboards=https://ci.opensearch.org/ci/dbc/bundle-build-dashboards/1.2.0/869/linux/x64
```

To run OpenSearch Dashboards integration tests with local artifacts on different distributions
```bash
./test.sh integ-test manifests/2.0.0/opensearch-dashboards-2.0.0-test.yml --paths opensearch=tar opensearch-dashboards=tar
./test.sh integ-test manifests/2.0.0/opensearch-dashboards-2.0.0-test.yml --paths opensearch=rpm opensearch-dashboards=rpm
```

:warning: RPM Test requires user to run the `./test.sh` command with sudo permission, as rpm requires root to install and start the service.

### Backwards Compatibility Tests

Runs backward compatibility invoking `run_bwc_test.py` in each component from a distribution manifest.
Expand Down
2 changes: 2 additions & 0 deletions src/test_workflow/integ_test/local_test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ def __init__(
self.dependency_installer = dependency_installer

self.service_opensearch = ServiceOpenSearch(
self.manifest.build.filename,
self.manifest.build.version,
self.manifest.build.distribution if self.manifest.build.distribution else 'tar',
self.additional_cluster_config,
self.security_enabled,
self.dependency_installer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,18 @@ def __init__(
self.dependency_installer_opensearch_dashboards = dependency_installer_opensearch_dashboards

self.service_opensearch = ServiceOpenSearch(
self.manifest_opensearch.build.filename,
self.manifest_opensearch.build.version,
self.manifest_opensearch.build.distribution,
{},
self.security_enabled,
self.dependency_installer_opensearch,
self.work_dir)

build = self.manifest_opensearch_dashboards.build

self.service_opensearch_dashboards = ServiceOpenSearchDashboards(
build.version,
self.manifest_opensearch_dashboards.build.filename,
self.manifest_opensearch_dashboards.build.version,
self.manifest_opensearch_dashboards.build.distribution,
self.additional_cluster_config,
self.security_enabled,
self.dependency_installer_opensearch_dashboards,
Expand Down
21 changes: 19 additions & 2 deletions src/test_workflow/integ_test/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import abc
import logging
import os
import time

import requests
Expand All @@ -19,14 +20,30 @@ class Service(abc.ABC):
Abstract base class for all types of test clusters.
"""

def __init__(self, work_dir, version, security_enabled, additional_config, dependency_installer):
def __init__(self, work_dir, filename, version, distribution, security_enabled, additional_config, dependency_installer):
self.filename = filename
self.work_dir = work_dir
self.version = version
self.distribution = distribution
self.security_enabled = security_enabled
self.additional_config = additional_config
self.dependency_installer = dependency_installer

self.process_handler = Process()
self.process_handler = Process(self.filename, self.distribution)
Copy link
Member

Choose a reason for hiding this comment

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

This class is a generic Service. Subclass it into ServiceOpenSearchTar and ServiceOpenSearchRpm, ServiceOpenSearchDashboardsTar etc., and implement the differences in the child classes.

self.install_dir_map = {
"tar": os.path.join(self.work_dir, f"{self.filename}-{self.version}"),
"rpm": os.path.join(os.sep, "usr", "share", self.filename)
}
self.config_file_map = {
"tar": os.path.join(self.install_dir_map[self.distribution], "config", f"{self.filename.replace('-', '_')}.yml"),
"rpm": os.path.join(os.sep, "etc", self.filename, f"{self.filename.replace('-', '_')}.yml")
}
self.start_cmd_map = {
"tar-opensearch": "./opensearch-tar-install.sh",
"tar-opensearch-dashboards": "./opensearch-dashboards",
"rpm-opensearch": "systemctl start opensearch",
"rpm-opensearch-dashboards": "systemctl start opensearch-dashboards"
}
self.install_dir = ""

@abc.abstractmethod
Expand Down
44 changes: 35 additions & 9 deletions src/test_workflow/integ_test/service_opensearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import logging
import os
import subprocess
import tarfile

import requests
Expand All @@ -17,22 +18,27 @@
class ServiceOpenSearch(Service):
def __init__(
self,
filename,
version,
distribution,
additional_config,
security_enabled,
dependency_installer,
work_dir
):
super().__init__(work_dir, version, security_enabled, additional_config, dependency_installer)
super().__init__(work_dir, filename, version, distribution, security_enabled, additional_config, dependency_installer)

self.dependency_installer = dependency_installer
self.filename = filename
self.distribution = distribution

self.install_dir = os.path.join(self.work_dir, f"opensearch-{self.version}")
logging.info(f'{self.filename} distribution: {self.distribution}')
self.install_dir = self.install_dir_map[self.distribution]

def start(self):
self.__download()

self.opensearch_yml_dir = os.path.join(self.install_dir, "config", "opensearch.yml")
self.opensearch_yml_dir = self.config_file_map[self.distribution]
self.security_plugin_dir = os.path.join(self.install_dir, "plugins", "opensearch-security")

if not self.security_enabled and os.path.isdir(self.security_plugin_dir):
Expand All @@ -41,7 +47,7 @@ def start(self):
if self.additional_config:
self.__add_plugin_specific_config(self.additional_config)

self.process_handler.start("./opensearch-tar-install.sh", self.install_dir)
self.process_handler.start(self.start_cmd_map[f"{self.distribution}-{self.filename}"], self.install_dir)
logging.info(f"Started OpenSearch with parent PID {self.process_handler.pid}")

def __download(self):
Expand All @@ -50,11 +56,31 @@ def __download(self):
bundle_name = self.dependency_installer.download_dist(self.work_dir)
logging.info(f"Downloaded bundle to {os.path.realpath(bundle_name)}")

logging.info(f"Unpacking {bundle_name}")
with tarfile.open(bundle_name, 'r') as bundle_tar:
bundle_tar.extractall(self.work_dir)

logging.info(f"Unpacked {bundle_name}")
logging.info(f"Installing {bundle_name} in {self.install_dir}")

if self.distribution == "tar":
Copy link
Member

Choose a reason for hiding this comment

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

Subclass ServiceOpenSearch into ServiceOpenSearchTar and ``ServiceOpenSearchRpm` and implement the differences there.

with tarfile.open(bundle_name, 'r') as bundle_tar:
bundle_tar.extractall(self.work_dir)
elif self.distribution == "rpm":
logging.info("rpm installation requires sudo, script will exit if current user does not have sudo access")
rpm_install_cmd = " ".join(
[
'yum',
'remove',
'-y',
self.filename,
'&&',
'yum',
'install',
'-y',
bundle_name
]
)
subprocess.check_call(rpm_install_cmd, cwd=self.work_dir, shell=True)
else:
raise(f'{self.distribution} is not supported in integ-test yet')

logging.info(f"Installed {bundle_name}")

def url(self, path=""):
return f'{"https" if self.security_enabled else "http"}://{self.endpoint()}:{self.port()}{path}'
Expand Down
46 changes: 36 additions & 10 deletions src/test_workflow/integ_test/service_opensearch_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@
class ServiceOpenSearchDashboards(Service):
def __init__(
self,
filename,
version,
distribution,
additional_config,
security_enabled,
dependency_installer,
work_dir
):
super().__init__(work_dir, version, security_enabled, additional_config, dependency_installer)
self.install_dir = os.path.join(self.work_dir, f"opensearch-dashboards-{self.version}")
super().__init__(work_dir, filename, version, distribution, security_enabled, additional_config, dependency_installer)
self.filename = filename
self.distribution = distribution

logging.info(f'{self.filename} distribution: {self.distribution}')
self.install_dir = self.install_dir_map[self.distribution]

def start(self):
logging.info(f"Starting OpenSearch Dashboards service from {self.work_dir}")
self.__download()

self.opensearch_dashboards_yml_dir = os.path.join(self.install_dir, "config", "opensearch_dashboards.yml")
self.opensearch_dashboards_yml_dir = self.config_file_map[self.distribution]
self.executable_dir = os.path.join(self.install_dir, "bin")

if not self.security_enabled:
Expand All @@ -42,7 +48,7 @@ def start(self):
if self.additional_config:
self.__add_plugin_specific_config(self.additional_config)

self.process_handler.start("./opensearch-dashboards", self.executable_dir)
self.process_handler.start(self.start_cmd_map[f"{self.distribution}-{self.filename}"], self.executable_dir)
logging.info(f"Started OpenSearch Dashboards with parent PID {self.process_handler.pid}")

def __set_logging_dest(self):
Expand All @@ -53,7 +59,7 @@ def __set_logging_dest(self):
def __remove_security(self):
self.security_plugin_dir = os.path.join(self.install_dir, "plugins", "securityDashboards")
if os.path.isdir(self.security_plugin_dir):
subprocess.check_call("./opensearch-dashboards-plugin remove securityDashboards", cwd=self.executable_dir, shell=True)
subprocess.check_call("./opensearch-dashboards-plugin remove --allow-root securityDashboards", cwd=self.executable_dir, shell=True)

with open(self.opensearch_dashboards_yml_dir, "w") as yamlfile:
yamlfile.close()
Expand All @@ -63,11 +69,31 @@ def __download(self):
bundle_name = self.dependency_installer.download_dist(self.work_dir)
logging.info(f"Downloaded bundle to {os.path.realpath(bundle_name)}")

logging.info(f"Unpacking {bundle_name}")
with tarfile.open(bundle_name, 'r') as bundle_tar:
bundle_tar.extractall(self.work_dir)

logging.info(f"Unpacked {bundle_name}")
logging.info(f"Installing {bundle_name} in {self.install_dir}")

if self.distribution == "tar":
with tarfile.open(bundle_name, 'r') as bundle_tar:
bundle_tar.extractall(self.work_dir)
elif self.distribution == "rpm":
logging.info("rpm installation requires sudo, script will exit if current user does not have sudo access")
rpm_install_cmd = " ".join(
[
'yum',
'remove',
'-y',
self.filename,
'&&',
'yum',
'install',
'-y',
bundle_name
]
)
subprocess.check_call(rpm_install_cmd, cwd=self.work_dir, shell=True)
else:
raise(f'{self.distribution} is not supported in integ-test yet')

logging.info(f"Installed {bundle_name}")

def url(self, path=""):
return f'http://{self.endpoint()}:{self.port()}{path}'
Expand Down
31 changes: 27 additions & 4 deletions tests/tests_system/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,30 @@
class TestProcess(unittest.TestCase):
def test(self) -> None:

process_handler = Process()
process_handler = Process("opensearch", "tar")

process_handler.start("./tests/tests_system/data/wait_for_input.sh", ".")

self.assertTrue(process_handler.started)
self.assertIsNotNone(process_handler.pid)
self.assertIsNotNone(process_handler.stdout_data)
self.assertIsNotNone(process_handler.stderr_data)

return_code = process_handler.terminate()

self.assertIsNone(return_code)
self.assertIsNotNone(process_handler.stdout_data)
self.assertIsNotNone(process_handler.stderr_data)

self.assertFalse(process_handler.started)
self.assertIsNone(process_handler.pid)

@patch('psutil.Process')
@patch('subprocess.check_output', return_value="MainPID=123")
@patch('subprocess.check_call', return_code=0)
def test_repm_pid(self, mock_subprocess_check_call: MagicMock, mock_subprocess_check_output: MagicMock, mock_psutil_process: MagicMock) -> None:

process_handler = Process("opensearch", "rpm")

process_handler.start("./tests/tests_system/data/wait_for_input.sh", ".")

Expand All @@ -34,12 +57,12 @@ def test(self) -> None:

@patch.object(tempfile, 'NamedTemporaryFile')
def test_file_open_mode(self, mock_tempfile: MagicMock) -> None:
process_handler = Process()
process_handler = Process("opensearch", "tar")
process_handler.start("./tests/tests_system/data/wait_for_input.sh", ".")
mock_tempfile.assert_has_calls([call(mode="r+"), call(mode="r+")])

def test_start_twice(self) -> None:
process_handler = Process()
process_handler = Process("opensearch", "tar")
process_handler.start("ls", ".")

with self.assertRaises(ProcessStartedError) as ctx:
Expand All @@ -48,7 +71,7 @@ def test_start_twice(self) -> None:
self.assertTrue(str(ctx.exception).startswith("Process already started, pid: "))

def test_terminate_unstarted_process(self) -> None:
process_handler = Process()
process_handler = Process("opensearch", "tar")

with self.assertRaises(ProcessNotStartedError) as ctx:
process_handler.terminate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class LocalTestClusterTests(unittest.TestCase):

def setUp(self):
mock_manifest = MagicMock()
mock_manifest.build.filename = "opensearch"
mock_manifest.build.version = "1.1.0"
mock_manifest.build.distribution = "tar"
self.manifest = mock_manifest

self.work_dir = "test_work_dir"
Expand Down Expand Up @@ -53,7 +55,9 @@ def test_start(self, mock_service):
cluster.start()

mock_service.assert_called_once_with(
"opensearch",
"1.1.0",
"tar",
self.additional_cluster_config,
self.security_enabled,
self.dependency_installer,
Expand Down
Loading