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 option to validate with/without security and new password for latest versions #4377

Merged
merged 5 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 6 additions & 2 deletions src/validation_workflow/api_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import base64
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
from typing import Any

import requests

from test_workflow.integ_test.utils import get_password

"""
This class is to run API test againt on local OpenSearch API URL with default port 9200.
It returns response status code and the response content.
Expand All @@ -18,9 +21,10 @@

class ApiTest:

def __init__(self, request_url: str) -> None:
def __init__(self, request_url: str, version: str) -> None:
self.request_url = request_url
self.apiHeaders_auth = {"Authorization": "Basic YWRtaW46YWRtaW4="} # default user/pass "admin/admin" in Base64 format
self.password = base64.b64encode(f"admin:{get_password(version)}".encode("utf-8")).decode("utf-8")
self.apiHeaders_auth = {"Authorization": f'Basic {self.password}'} # user/pass "admin/pass" in Base64 format fetched from get_password() method
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
self.apiHeaders_accept = {"Accept": "*/*"}
self.apiHeaders_content_type = {"Content-Type": "application/json"}
self.apiHeaders = {}
Expand Down
9 changes: 7 additions & 2 deletions src/validation_workflow/api_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self) -> None:
pass

@staticmethod
def test_apis(projects: list) -> Any:
def test_apis(version: str, projects: list, security_plugin_exists: bool = True) -> Any:
pass_counter, fail_counter = 0, 0

# the test case parameters are formated as ['<request_url>',<success_status_code>,'<validate_string(optional)>']
Expand All @@ -30,6 +30,11 @@ def test_apis(projects: list) -> Any:
['https://localhost:9200/_cat/plugins?v', 200, ''],
['https://localhost:9200/_cat/health?v', 200, 'green'],
]
# Use http in the request url if the security plugin is absent
if not security_plugin_exists:
for api in test_apis:
api[0] = "http" + api[0][5:] # type: ignore
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved

if ("opensearch-dashboards" in projects):
test_apis.append(['http://localhost:5601/api/status', 200, ''])

Expand All @@ -38,7 +43,7 @@ def test_apis(projects: list) -> Any:
success_status_code = test_api.__getitem__(1)
validate_string = test_api.__getitem__(2)

status_code, response_text = ApiTest(str(request_url)).api_get()
status_code, response_text = ApiTest(str(request_url), version).api_get()
logging.info(f"\nRequest_url ->{str(request_url)} \n")
logging.info(f"\nStatus_code ->{status_code} \nresponse_text ->{response_text}")

Expand Down
4 changes: 2 additions & 2 deletions src/validation_workflow/docker/validation_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def validation(self) -> bool:

if self.check_cluster_readiness():
# STEP 4 . OS, OSD API validation
_test_result, _counter = ApiTestCases().test_apis(self.args.projects)
_test_result, _counter = ApiTestCases().test_apis(self.args.version, self.args.projects, True)

if _test_result:
logging.info(f'All tests Pass : {_counter}')
Expand Down Expand Up @@ -137,7 +137,7 @@ def check_http_request(self) -> bool:

for url, name in self.test_readiness_urls.items():
try:
status_code, response_text = ApiTest(url).api_get()
status_code, response_text = ApiTest(url, self.args.version).api_get()
if status_code != 200:
logging.error(f'Error connecting to {name} ({url}): status code {status_code}')
return False
Expand Down
9 changes: 7 additions & 2 deletions src/validation_workflow/rpm/validation_rpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from system.execute import execute
from system.temporary_directory import TemporaryDirectory
from test_workflow.integ_test.utils import get_password
from validation_workflow.api_test_cases import ApiTestCases
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation import Validation
Expand All @@ -32,6 +33,7 @@ def download_artifacts(self) -> bool:
if ("https:" not in self.args.file_path.get(project)):
self.copy_artifact(self.args.file_path.get(project), str(self.tmp_dir.path))
else:
self.args.version = self.get_version(self.args.file_path.get(project))
self.check_url(self.args.file_path.get(project))
else:
if (self.args.artifact_type == "staging"):
Expand All @@ -47,7 +49,10 @@ def installation(self) -> bool:
for project in self.args.projects:
self.filename = os.path.basename(self.args.file_path.get(project))
execute(f'sudo yum remove {project} -y', ".")
execute(f'sudo rpm -ivh {os.path.join(self.tmp_dir.path, self.filename)}', str(self.tmp_dir.path), True, False)
execute(f'sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD={get_password(str(self.args.version))} rpm -ivh {os.path.join(self.tmp_dir.path, self.filename)}', str(self.tmp_dir.path), True, False) # noqa: 501

if self.args.allow_without_security:
self.args.allow_without_security = self.test_security_plugin("/usr/")
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
except:
raise Exception('Failed to install Opensearch')
return True
Expand All @@ -68,7 +73,7 @@ def start_cluster(self) -> bool:
return True

def validation(self) -> bool:
test_result, counter = ApiTestCases().test_apis(self.args.projects)
test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, self.args.allow_without_security)
if (test_result):
logging.info(f'All tests Pass : {counter}')
return True
Expand Down
9 changes: 7 additions & 2 deletions src/validation_workflow/tar/validation_tar.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from system.execute import execute
from system.process import Process
from system.temporary_directory import TemporaryDirectory
from test_workflow.integ_test.utils import get_password
from validation_workflow.api_test_cases import ApiTestCases
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation import Validation
Expand All @@ -35,6 +36,7 @@ def download_artifacts(self) -> bool:
if ("https:" not in self.args.file_path.get(project)):
self.copy_artifact(self.args.file_path.get(project), str(self.tmp_dir.path))
else:
self.args.version = self.get_version(self.args.file_path.get(project))
self.check_url(self.args.file_path.get(project))
else:
if (self.args.artifact_type == "staging"):
Expand All @@ -49,13 +51,15 @@ def installation(self) -> bool:
for project in self.args.projects:
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
if self.args.allow_without_security:
self.args.allow_without_security = self.test_security_plugin(str(self.tmp_dir.path))
except:
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", "opensearch-tar-install.sh"), ".")
self.os_process.start(f'export OPENSEARCH_INITIAL_ADMIN_PASSWORD={get_password(str(self.args.version))} && ./opensearch-tar-install.sh', os.path.join(self.tmp_dir.path, "opensearch"))
time.sleep(85)
if ("opensearch-dashboards" in self.args.projects):
self.osd_process.start(os.path.join(str(self.tmp_dir.path), "opensearch-dashboards", "bin", "opensearch-dashboards"), ".")
Expand All @@ -66,7 +70,8 @@ def start_cluster(self) -> bool:
return True

def validation(self) -> bool:
test_result, counter = ApiTestCases().test_apis(self.args.projects)

test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, self.args.allow_without_security)
if (test_result):
logging.info(f'All tests Pass : {counter}')
else:
Expand Down
14 changes: 14 additions & 0 deletions src/validation_workflow/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@


import logging
import os
import re
import shutil
from abc import ABC, abstractmethod
from typing import Any

from system.execute import execute
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation_args import ValidationArgs

Expand Down Expand Up @@ -38,6 +41,17 @@ def copy_artifact(self, filepath: str, tempdir_path: str) -> bool:
else:
raise Exception("Provided path for local artifacts does not exist")

def test_security_plugin(self, work_dir: str) -> bool:
(_, path, _) = execute(f'find {work_dir} -type f -iname \'opensearch-plugin\'', ".", True, False)
if (path):
(_, list_plugins, _) = execute("./opensearch-plugin list", path.replace("opensearch-plugin", "").rstrip("\n"), True, False)
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
return "opensearch-security" in list_plugins
else:
raise Exception("Couldn't fetch the path to plugin folder")

def get_version(self, project: str) -> str:
return re.search(r'(\d+\.\d+\.\d+)', os.path.basename(project)).group(1)
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved

def run(self) -> Any:
try:
return self.download_artifacts() and self.installation() and self.start_cluster() and self.validation() and self.cleanup()
Expand Down
10 changes: 9 additions & 1 deletion src/validation_workflow/validation_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ def __init__(self) -> None:
help="Enter type of artifacts that needs to be validated",
choices=["staging", "production"],
default="production",
dest="artifact_type",
dest="artifact_type"
)
parser.add_argument(
"-s",
"--allow-without-security",
action="store_true",
default=False,
help="Allow Validation without security"
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
Expand All @@ -136,6 +143,7 @@ def __init__(self) -> None:
self.version = args.version
self.file_path = args.file_path
self.artifact_type = args.artifact_type
self.allow_without_security = args.allow_without_security
self.logging_level = args.logging_level
self.distribution = args.distribution
self.platform = args.platform
Expand Down
12 changes: 7 additions & 5 deletions src/validation_workflow/yum/validation_yum.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import logging
import os
import re
import time

from system.execute import execute
from system.temporary_directory import TemporaryDirectory
from test_workflow.integ_test.utils import get_password
from validation_workflow.api_test_cases import ApiTestCases
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation import Validation
Expand All @@ -33,7 +33,7 @@ def download_artifacts(self) -> bool:
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.args.version = self.get_version(self.args.file_path.get(project))
self.check_url(self.args.file_path.get(project))

else:
Expand All @@ -53,7 +53,9 @@ def installation(self) -> bool:
logging.info('Removed previous versions of Opensearch')
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", ".")
execute(f"sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD={get_password(str(self.args.version))} yum install '{project}-{self.args.version}' -y", ".")
if self.args.allow_without_security:
self.args.allow_without_security = self.test_security_plugin("/usr/")
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
except:
raise Exception('Failed to install Opensearch')
return True
Expand All @@ -69,12 +71,12 @@ def start_cluster(self) -> bool:
return True

def validation(self) -> bool:
test_result, counter = ApiTestCases().test_apis(self.args.projects)
test_result, counter = ApiTestCases().test_apis(self.args.version, self.args.projects, self.args.allow_without_security)
if (test_result):
logging.info(f'All tests Pass : {counter}')
return True
else:
raise Exception(f'Some test cases failed : {counter}')
raise Exception(f'Not all tests Pass : {counter}')

def cleanup(self) -> bool:
try:
Expand Down
15 changes: 13 additions & 2 deletions tests/tests_validation_workflow/test_api_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@

class TestApiTest(unittest.TestCase):

@patch('validation_workflow.api_request.requests.get')
def test_api_get_new_password(self, mock_get: Mock) -> None:
mock_get.return_value.status_code = 200
mock_get.return_value.text = '{"key": "value"}'
request_url = 'https://localhost:9200'
api_test = ApiTest(request_url, "2.12.0")
status_code, response_text = api_test.api_get()
self.assertEqual(status_code, 200)
self.assertEqual(response_text, '{"key": "value"}')
mock_get.assert_called_once_with(request_url, headers={'Authorization': 'Basic YWRtaW46bXlTdHJvbmdQYXNzd29yZDEyMyE=', 'Accept': '*/*', 'Content-Type': 'application/json'}, verify=False)

@patch('validation_workflow.api_request.requests.get')
def test_api_get(self, mock_get: Mock) -> None:
mock_get.return_value.status_code = 200
mock_get.return_value.text = '{"key": "value"}'
request_url = 'http://localhost:9200'
api_test = ApiTest(request_url)
request_url = 'https://localhost:9200'
api_test = ApiTest(request_url, "2.3.0")
status_code, response_text = api_test.api_get()
self.assertEqual(status_code, 200)
self.assertEqual(response_text, '{"key": "value"}')
Expand Down
13 changes: 11 additions & 2 deletions tests/tests_validation_workflow/test_api_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class TestTestCases(unittest.TestCase):
def test_opensearch(self, mock_api_get: Mock) -> None:
mock_api_get.return_value = (200, 'green')
testcases = ApiTestCases()
result = testcases.test_apis(['opensearch'])
result = testcases.test_apis("1.3.0", ['opensearch'])

self.assertEqual(result[1], 'There are 3/3 test cases Pass')
self.assertEqual(mock_api_get.call_count, 3)
Expand All @@ -25,11 +25,20 @@ def test_opensearch(self, mock_api_get: Mock) -> None:
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'])
result = testcases.test_apis("2.1.1", ['opensearch', 'opensearch-dashboards'])

self.assertEqual(result[1], 'There are 4/4 test cases Pass')
self.assertEqual(mock_api_get.call_count, 4)

@patch('validation_workflow.api_test_cases.ApiTest.api_get')
def test_without_security(self, mock_api_get: Mock) -> None:
mock_api_get.return_value = (200, 'green')
testcases = ApiTestCases()
result = testcases.test_apis("1.3.0", ['opensearch'], False)

self.assertEqual(result[1], 'There are 3/3 test cases Pass')
self.assertEqual(mock_api_get.call_count, 3)


if __name__ == '__main__':
unittest.main()
37 changes: 37 additions & 0 deletions tests/tests_validation_workflow/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,40 @@ def test_copy_artifact(self, mock_validation_args: Mock, mock_copy: Mock) -> Non

result = mock_validation.copy_artifact(url, "tmp/tthcdhfh/")
self.assertTrue(result)

@patch('validation_workflow.validation.execute')
@patch('validation_workflow.tar.validation_tar.ValidationArgs')
def test_is_allow_with_security_true(self, mock_validation_args: Mock, mock_execute: Mock) -> None:
mock_execute.return_value = (0, "opensearch-security", "")

mock_validation_args.projects.return_value = ["opensearch"]
mock_validation = ValidateTar(mock_validation_args.return_value)

result = mock_validation.test_security_plugin("/bin/opensearch")

self.assertTrue(result)

@patch('validation_workflow.validation.execute')
@patch('validation_workflow.tar.validation_tar.ValidationArgs')
def test_is_allow_with_security_false(self, mock_validation_args: Mock, mock_execute: Mock) -> None:
mock_execute.return_value = (0, "opensearch", "")

mock_validation_args.projects.return_value = ["opensearch"]
mock_validation = ValidateTar(mock_validation_args.return_value)

result = mock_validation.test_security_plugin("/bin/opensearch")

self.assertFalse(result)

@patch('validation_workflow.validation.execute')
@patch('validation_workflow.tar.validation_tar.ValidationArgs')
def test_is_allow_with_security_exception(self, mock_validation_args: Mock, mock_execute: Mock) -> None:
mock_execute.return_value = (0, "", "error")

mock_validation_args.projects.return_value = ["opensearch"]
mock_validation = ValidateTar(mock_validation_args.return_value)

with self.assertRaises(Exception) as context:
mock_validation.test_security_plugin("/bin/opensearch")

self.assertEqual(str(context.exception), "Couldn't fetch the path to plugin folder")
4 changes: 4 additions & 0 deletions tests/tests_validation_workflow/test_validation_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def test_file_path(self) -> None:
def test_artifact_type(self) -> None:
self.assertNotEqual(ValidationArgs().artifact_type, "production")

@patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.6", "--distribution", "rpm", "--artifact-type", "staging", "--os-build-number", "1234", "--osd-build-number", "2312", "--allow-without-security"]) # noqa: E501
def test_allow_without_security(self) -> None:
self.assertEqual(ValidationArgs().allow_without_security, True)

@patch("argparse._sys.argv", [VALIDATION_PY, "--version", "1.3.0", "--projects", "opensearch"])
def test_set_projects(self) -> None:
self.assertEqual(ValidationArgs().projects, ["opensearch"])
Expand Down
Loading
Loading