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 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
14 changes: 9 additions & 5 deletions src/test_workflow/integ_test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import base64

import semver


def get_password(version: str) -> str:
def str_to_base64(value: str) -> str:
return base64.b64encode(value.encode("utf-8")).decode("utf-8")


def get_password(version: str, convert_to_base64: bool = False) -> str:
# Starting in 2.12.0, demo configuration setup script requires a strong password
if semver.compare(version, '2.12.0') != -1:
return "myStrongPassword123!"
else:
return "admin"
password = "myStrongPassword123!" if semver.compare(version, '2.12.0') != -1 else "admin"
return str_to_base64(password) if convert_to_base64 else password
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 4 additions & 2 deletions src/validation_workflow/api_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import requests

from test_workflow.integ_test.utils import get_password, str_to_base64

"""
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 +20,9 @@

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.apiHeaders_auth = {"Authorization": f'Basic {str_to_base64("admin:" + get_password(version))}'} # 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
12 changes: 7 additions & 5 deletions src/validation_workflow/api_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ 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
protocol_prefix = "https" if security_plugin_exists else "http"

# the test case parameters are formated as ['<request_url>',<success_status_code>,'<validate_string(optional)>']
test_apis = [
['https://localhost:9200/', 200, ''],
['https://localhost:9200/_cat/plugins?v', 200, ''],
['https://localhost:9200/_cat/health?v', 200, 'green'],
[f'{protocol_prefix}://localhost:9200/', 200, ''],
[f'{protocol_prefix}://localhost:9200/_cat/plugins?v', 200, ''],
[f'{protocol_prefix}://localhost:9200/_cat/health?v', 200, 'green'],
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 +40,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
6 changes: 4 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,7 @@ 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
except:
raise Exception('Failed to install Opensearch')
return True
Expand All @@ -68,7 +70,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.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if not self.args.force_https else True) # noqa: E501
if (test_result):
logging.info(f'All tests Pass : {counter}')
return True
Expand Down
6 changes: 4 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 @@ -55,7 +57,7 @@ def installation(self) -> bool:

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 +68,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.check_for_security_plugin(os.path.join(self.tmp_dir.path, "opensearch")) if not self.args.force_https else True) # noqa: E501
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 @@ -7,6 +7,8 @@


import logging
import os
import re
import shutil
from abc import ABC, abstractmethod
from typing import Any
Expand Down Expand Up @@ -38,6 +40,13 @@ def copy_artifact(self, filepath: str, tempdir_path: str) -> bool:
else:
raise Exception("Provided path for local artifacts does not exist")

def check_for_security_plugin(self, work_dir: str) -> bool:
path = os.path.exists(os.path.join(work_dir, "plugins", "opensearch-security"))
peterzhuamazon marked this conversation as resolved.
Show resolved Hide resolved
return path

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(
"-f",
"--force-https",
action="store_true",
default=True,
help="If False, use http/https to connect based on the existence of security plugin, else always use https, default to True"
)
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.force_https = args.force_https
self.logging_level = args.logging_level
self.distribution = args.distribution
self.platform = args.platform
Expand Down
11 changes: 6 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,8 @@ 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", ".")

except:
raise Exception('Failed to install Opensearch')
return True
Expand All @@ -69,12 +70,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.check_for_security_plugin(os.path.join(os.sep, "usr", "share", "opensearch")) if not self.args.force_https else True) # noqa: E501
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import base64
import unittest

from test_workflow.integ_test.utils import get_password
from test_workflow.integ_test.utils import get_password, str_to_base64


class TestUtils(unittest.TestCase):
def test_strong_password(self) -> None:
self.assertEqual("admin", get_password("2.11.1"))
self.assertEqual("myStrongPassword123!", get_password("3.0.0"))
self.assertEqual("YWRtaW4=", get_password("2.11.1", True))
self.assertEqual("bXlTdHJvbmdQYXNzd29yZDEyMyE=", get_password("2.12.0", True))

def test_str_to_base64(self) -> None:
value = "admin"
result = base64.b64encode(value.encode("utf-8")).decode("utf-8")
self.assertEqual(str_to_base64(value), result)
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'], True)

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'], True)

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()
12 changes: 12 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,15 @@ 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('os.path.exists')
@patch('validation_workflow.tar.validation_tar.ValidationArgs')
def test_check_for_security_plugin(self, mock_validation_args: Mock, mock_path_exists: Mock) -> None:
mock_path_exists.return_value = True

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

result = mock_validation.check_for_security_plugin("/tmp/tmkuiuo/opensearch")

self.assertTrue(result)
6 changes: 5 additions & 1 deletion 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", "--force-https"]) # noqa: E501
def test_force_https(self) -> None:
self.assertEqual(ValidationArgs().force_https, 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 All @@ -78,7 +82,7 @@ def test_projects_exception(self) -> None:
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"])
@patch("argparse._sys.argv", [VALIDATION_PY, "--file-path", "opensearch=https://opensearch.org/releases/opensearch/2.8.0/opensearch-2.8.0-linux-x64.xyz"])
def test_file_path_distribution_type(self) -> None:
with self.assertRaises(Exception) as ctx:
self.assertEqual(ValidationArgs().projects, ["opensearch"])
Expand Down
Loading
Loading