Skip to content

Commit

Permalink
Add validation for with/without security plugin and apply newpassword…
Browse files Browse the repository at this point in the history
… for latest versions

Signed-off-by: Divya Madala <divyaasm@amazon.com>
  • Loading branch information
Divyaasm committed Jan 27, 2024
1 parent 82e8c42 commit d09f2b2
Show file tree
Hide file tree
Showing 16 changed files with 192 additions and 56 deletions.
9 changes: 6 additions & 3 deletions src/validation_workflow/api_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
# compatible open source license.

from typing import Any
from test_workflow.integ_test.utils import get_password

import requests
import base64


"""
This class is to run API test againt on local OpenSearch API URL with default port 9200.
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
self.apiHeaders_accept = {"Accept": "*/*"}
self.apiHeaders_content_type = {"Content-Type": "application/json"}
self.apiHeaders = {}
Expand All @@ -29,6 +33,5 @@ def __init__(self, request_url: str) -> None:
self.apiHeaders.update(self.apiHeaders_content_type)

def api_get(self) -> Any:

response = requests.get(self.request_url, headers=self.apiHeaders, verify=False)
return response.status_code, response.text
10 changes: 8 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, allow_with_security: 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,15 +30,21 @@ def test_apis(projects: list) -> Any:
['https://localhost:9200/_cat/plugins?v', 200, ''],
['https://localhost:9200/_cat/health?v', 200, 'green'],
]
if not allow_with_security:
for api in test_apis:
api[0] = "http" + api[0][5:] # type: ignore

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

logging.info("Inside api-tests")

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()
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
13 changes: 9 additions & 4 deletions src/validation_workflow/rpm/validation_rpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation import Validation
from validation_workflow.validation_args import ValidationArgs
from test_workflow.integ_test.utils import get_password


class ValidateRpm(Validation, DownloadUtils):
Expand Down Expand Up @@ -47,7 +48,11 @@ 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/")

except:
raise Exception('Failed to install Opensearch')
return True
Expand All @@ -57,7 +62,7 @@ def start_cluster(self) -> bool:
for project in self.args.projects:
execute(f'sudo systemctl start {project}', ".")
time.sleep(20)
(stdout, stderr, status) = execute(f'sudo systemctl status {project}', ".")
(stdout, stderr, status) = execute(f'sudo systemctl status {project}', ".", True, False)
if(status == 0):
logging.info(stdout)
else:
Expand All @@ -68,8 +73,8 @@ def start_cluster(self) -> bool:
return True

def validation(self) -> bool:
test_result, counter = ApiTestCases().test_apis(self.args.projects)
if (test_result):
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:
Expand Down
8 changes: 6 additions & 2 deletions src/validation_workflow/tar/validation_tar.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation import Validation
from validation_workflow.validation_args import ValidationArgs
from test_workflow.integ_test.utils import get_password


class ValidateTar(Validation, DownloadUtils):
Expand Down Expand Up @@ -49,13 +50,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 +69,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
9 changes: 9 additions & 0 deletions src/validation_workflow/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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 +39,14 @@ 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:
(_, stdout_1, _) = execute(f'find {work_dir} -type f -iname \'opensearch-plugin\'', ".", True, False)
if (stdout_1):
(_, stdout_2, _) = execute("./opensearch-plugin list", stdout_1.replace("opensearch-plugin", "").rstrip("\n"), True, False)
return "opensearch-security" in stdout_2
else:
raise Exception("Couldn't fetch the path to plugin folder")

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"
)
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
9 changes: 6 additions & 3 deletions src/validation_workflow/yum/validation_yum.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from validation_workflow.download_utils import DownloadUtils
from validation_workflow.validation import Validation
from validation_workflow.validation_args import ValidationArgs
from test_workflow.integ_test.utils import get_password


class ValidateYum(Validation, DownloadUtils):
Expand Down Expand Up @@ -53,7 +54,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/")
except:
raise Exception('Failed to install Opensearch')
return True
Expand All @@ -69,12 +72,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
4 changes: 2 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,7 +25,7 @@ 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)
Expand Down
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("/path/to/work_dir")

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("/path/to/work_dir")

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("/path/to/work_dir")

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
5 changes: 3 additions & 2 deletions tests/tests_validation_workflow/test_validation_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ def test_download_artifacts(self, mock_is_container_daemon_running: Mock, mock_v
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.version = '1.0.0.1000'
mock_validation_args.return_value.version = '1.0.0'
mock_validation_args.return_value.validate_digest_only = False
mock_validation_args.return_value.allow_without_security = False
mock_validation_args.return_value.projects = ["opensearch"]
mock_docker_image.return_value = MagicMock()
mock_container.return_value = (True, 'test_file.yml')
Expand All @@ -69,7 +70,7 @@ def test_staging(self, mock_time_sleep: Mock, mock_digest: Mock, mock_container:
# 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_apis(['opensearch'])])
mock_test.assert_has_calls([call(), call().test_apis("1.0.0", ['opensearch'], True)])

@patch('validation_workflow.docker.validation_docker.ValidateDocker.check_http_request')
@patch('validation_workflow.docker.validation_docker.ValidationArgs')
Expand Down
Loading

0 comments on commit d09f2b2

Please sign in to comment.