diff --git a/.github/workflows/e2eTest.yml b/.github/workflows/e2eTest.yml index 6380c1a85..7393ca28c 100644 --- a/.github/workflows/e2eTest.yml +++ b/.github/workflows/e2eTest.yml @@ -44,4 +44,4 @@ jobs: run: | cd test chmod +x ./tests.py - pytest -n 3 tests.py + pytest tests.py --unique_id="testindex" diff --git a/test/README.md b/test/README.md index e7fbe1534..957c66ba1 100644 --- a/test/README.md +++ b/test/README.md @@ -24,14 +24,52 @@ pytest tests.py The test script, by default, uses the ports assigned to the containers in this [docker-compose file](../TrafficCapture/dockerSolution/src/main/docker/docker-compose.yml), so if the Docker solution in its current setup started with no issues, then the test script will run as is. If for any reason -the user changed the ports in that file, they must also either, change the following environment variables: -`PROXY_ENDPOINT`, `SOURCE_ENDPOINT`, and `TARGET_ENDPOINT` respectively, or update the default value -(which can be found below) for them in [tests.py](tests.py). - -The following are the default values for the only endpoints touched by this script: -* `PROXY_ENDPOINT = https://localhost:9200` -* `SOURCE_ENDPOINT = http://localhost:19200` -* `TARGET_ENDPOINT = https://localhost:29200` +the user changed the ports in that file, they must also either, provide the following parameters variables: +`proxy_endpoint`, `source_endpoint`, and `target_endpoint` respectively, or update the default value + for them in [conftest.py](conftest.py). + + +#### Script Parameters + +This script accepts various parameters to customize its behavior. Below is a list of available parameters along with their default values and acceptable choices: + +- `--proxy_endpoint`: The endpoint for the proxy endpoint. + - Default: `https://localhost:9200` + +- `--source_endpoint`: The endpoint for the source endpoint. + - Default: `https://localhost:19200` + +- `--target_endpoint`: The endpoint for the target endpoint. + - Default: `https://localhost:29200` + +- `--source_auth_type`: Specifies the authentication type for the source endpoint. + - Default: `basic` + - Choices: `none`, `basic`, `sigv4` + +- `--source_verify_ssl`: Determines whether to verify the SSL certificate for the source endpoint. + - Default: `False` + - Choices: `True`, `False` + +- `--target_auth_type`: Specifies the authentication type for the target endpoint. + - Default: `basic` + - Choices: `none`, `basic`, `sigv4` + +- `--target_verify_ssl`: Determines whether to verify the SSL certificate for the target endpoint. + - Default: `False` + - Choices: `True`, `False` + +- `--source_username`: Username for authentication with the source endpoint. + - Default: `admin` + +- `--source_password`: Password for authentication with the source endpoint. + - Default: `admin` + +- `--target_username`: Username for authentication with the target endpoint. + - Default: `admin` + +- `--target_password`: Password for authentication with the target endpoint. + - Default: `admin` + #### Clean Up The test script is implemented with a setup and teardown functions that are ran after diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 000000000..c88069a1d --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,95 @@ +# conftest.py +import pytest + +import logging + + +def pytest_configure(config): + # Configure logging + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + + # This line ensures that log messages are displayed on the console during test runs + logging.getLogger().setLevel(logging.DEBUG) + + +def pytest_addoption(parser): + parser.addoption("--proxy_endpoint", action="store", default="https://localhost:9200") + parser.addoption("--source_endpoint", action="store", default="https://localhost:19200") + parser.addoption("--target_endpoint", action="store", default="https://localhost:29200") + parser.addoption("--source_auth_type", action="store", default="basic", choices=["none", "basic", "sigv4"]) + parser.addoption("--source_verify_ssl", action="store", default="False", choices=["True", "False"]) + parser.addoption("--target_auth_type", action="store", default="basic", choices=["none", "basic", "sigv4"]) + parser.addoption("--target_verify_ssl", action="store", default="False", choices=["True", "False"]) + parser.addoption("--deployment_type", action="store", default="local", choices=["local", "cloud"]) + parser.addoption("--source_username", action="store", default="admin") + parser.addoption("--source_password", action="store", default="admin") + parser.addoption("--target_username", action="store", default="admin") + parser.addoption("--target_password", action="store", default="admin") + parser.addoption("--unique_id", action="store", default="") + + +@pytest.fixture +def proxy_endpoint(pytestconfig): + return pytestconfig.getoption("proxy_endpoint") + + +@pytest.fixture +def source_endpoint(pytestconfig): + return pytestconfig.getoption("source_endpoint") + + +@pytest.fixture +def target_endpoint(pytestconfig): + return pytestconfig.getoption("target_endpoint") + + +@pytest.fixture +def source_auth_type(pytestconfig): + return pytestconfig.getoption("source_auth_type") + + +@pytest.fixture +def source_username(pytestconfig): + return pytestconfig.getoption("source_username") + + +@pytest.fixture +def source_password(pytestconfig): + return pytestconfig.getoption("source_password") + + +@pytest.fixture +def target_auth_type(pytestconfig): + return pytestconfig.getoption("target_auth_type") + + +@pytest.fixture +def target_username(pytestconfig): + return pytestconfig.getoption("target_username") + + +@pytest.fixture +def target_password(pytestconfig): + return pytestconfig.getoption("target_password") + + +@pytest.fixture +def target_verify_ssl(pytestconfig): + return pytestconfig.getoption("target_verify_ssl") + + +@pytest.fixture +def source_verify_ssl(pytestconfig): + return pytestconfig.getoption("source_verify_ssl") + + +@pytest.fixture +def deployment_type(pytestconfig): + return pytestconfig.getoption("deployment_type") + + +@pytest.fixture +def unique_id(pytestconfig): + return pytestconfig.getoption("unique_id") diff --git a/test/operations.py b/test/operations.py index 541073969..f3a7d986f 100644 --- a/test/operations.py +++ b/test/operations.py @@ -1,49 +1,49 @@ import requests import json -from typing import Optional, Tuple -def create_index(endpoint: str, index_name: str, auth: Optional[Tuple[str, str]] = None): - response = requests.put(f'{endpoint}/{index_name}', auth=auth, verify=False) +def create_index(endpoint: str, index_name: str, auth, verify_ssl: bool = False): + response = requests.put(f'{endpoint}/{index_name}', auth=auth, verify=verify_ssl) return response -def check_index(endpoint: str, index_name: str, auth: Optional[Tuple[str, str]] = None): - response = requests.get(f'{endpoint}/{index_name}', auth=auth, verify=False) +def check_index(endpoint: str, index_name: str, auth, verify_ssl: bool = False): + response = requests.get(f'{endpoint}/{index_name}', auth=auth, verify=verify_ssl) return response -def delete_index(endpoint: str, index_name: str, auth: Optional[Tuple[str, str]] = None): - response = requests.delete(f'{endpoint}/{index_name}', auth=auth, verify=False) +def delete_index(endpoint: str, index_name: str, auth, verify_ssl: bool = False): + response = requests.delete(f'{endpoint}/{index_name}', auth=auth, verify=verify_ssl) return response -def delete_document(endpoint: str, index_name: str, doc_id: str, auth: Optional[Tuple[str, str]] = None): - response = requests.delete(f'{endpoint}/{index_name}/_doc/{doc_id}', auth=auth, verify=False) +def delete_document(endpoint: str, index_name: str, doc_id: str, auth, + verify_ssl: bool = False): + response = requests.delete(f'{endpoint}/{index_name}/_doc/{doc_id}', auth=auth, verify=verify_ssl) return response -def create_document(endpoint: str, index_name: str, doc_id: str, auth: Optional[Tuple[str, str]] = None): +def create_document(endpoint: str, index_name: str, doc_id: str, auth, + verify_ssl: bool = False): document = { 'title': 'Test Document', 'content': 'This is a sample document for testing OpenSearch.' } url = f'{endpoint}/{index_name}/_doc/{doc_id}' headers = {'Content-Type': 'application/json'} - - response = requests.put(url, headers=headers, data=json.dumps(document), auth=auth, verify=False) + response = requests.put(url, headers=headers, data=json.dumps(document), auth=auth, verify=verify_ssl) return response -def get_document(endpoint: str, index_name: str, doc_id: str, auth: Optional[Tuple[str, str]] = None): +def get_document(endpoint: str, index_name: str, doc_id: str, auth, + verify_ssl: bool = False): url = f'{endpoint}/{index_name}/_doc/{doc_id}' headers = {'Content-Type': 'application/json'} - - response = requests.get(url, headers=headers, auth=auth, verify=False) + response = requests.get(url, headers=headers, auth=auth, verify=verify_ssl) return response diff --git a/test/requirements.txt b/test/requirements.txt index cd894ab19..98a491507 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -8,3 +8,5 @@ pytest==7.3.1 pytest-xdist==3.3.1 requests==2.31.0 urllib3==2.0.7 +requests_aws4auth +boto3 \ No newline at end of file diff --git a/test/tests.py b/test/tests.py index f07eea681..2febde075 100644 --- a/test/tests.py +++ b/test/tests.py @@ -6,20 +6,22 @@ from http import HTTPStatus from typing import Tuple, Callable import unittest -import os import logging import time import requests -import uuid import string import secrets +import pytest +import boto3 +from requests_aws4auth import AWS4Auth + from requests.exceptions import ConnectionError, SSLError logger = logging.getLogger(__name__) -def get_indices(endpoint, auth): - response = requests.get(f'{endpoint}/_cat/indices', auth=auth, verify=False) +def get_indices(endpoint, auth, verify): + response = requests.get(f'{endpoint}/_cat/indices', auth=auth, verify=verify) indices = [] response_lines = response.text.strip().split('\n') for line in response_lines: @@ -29,8 +31,8 @@ def get_indices(endpoint, auth): return indices -def get_doc_count(endpoint, index, auth): - response = requests.get(f'{endpoint}/{index}/_count', auth=auth, verify=False) +def get_doc_count(endpoint, index, auth, verify): + response = requests.get(f'{endpoint}/{index}/_count', auth=auth, verify=verify) count = json.loads(response.text)['count'] return count @@ -69,27 +71,54 @@ def retry_request(request: Callable, args: Tuple = (), max_attempts: int = 15, d class E2ETests(unittest.TestCase): + + @pytest.fixture(autouse=True) + def init_fixtures(self, proxy_endpoint, source_endpoint, target_endpoint, source_auth_type, source_username, + source_password, target_auth_type, target_username, target_password, target_verify_ssl, + source_verify_ssl, deployment_type, unique_id): + self.proxy_endpoint = proxy_endpoint + self.source_endpoint = source_endpoint + self.target_endpoint = target_endpoint + self.source_auth_type = source_auth_type + self.source_auth = self.setup_authentication(source_auth_type, source_username, source_password) + self.source_username = source_username + self.source_password = source_password + self.target_auth_type = target_auth_type + self.target_auth = self.setup_authentication(target_auth_type, target_username, target_password) + self.target_username = target_username + self.target_password = target_password + self.source_verify_ssl = source_verify_ssl.lower() == 'true' + self.target_verify_ssl = target_verify_ssl.lower() == 'true' + self.deployment_type = deployment_type + self.unique_id = unique_id + + def setup_authentication(self, auth_type, username, password): + if auth_type == "basic": + return (username, password) + elif auth_type == "sigv4": + session = boto3.Session() + credentials = session.get_credentials() + aws_auth = AWS4Auth(credentials.access_key, credentials.secret_key, session.region_name, 'es', + session_token=credentials.token) + return aws_auth + return None + def set_common_values(self): - self.proxy_endpoint = os.getenv('PROXY_ENDPOINT', 'https://localhost:9200') - self.source_endpoint = os.getenv('SOURCE_ENDPOINT', 'https://localhost:19200') - self.target_endpoint = os.getenv('TARGET_ENDPOINT', 'https://localhost:29200') - self.username = os.getenv('username', 'admin') - self.password = os.getenv('password', 'admin') - self.auth = (self.username, self.password) - self.index = f"my_index_{uuid.uuid4()}" + self.index = f"test_index_{self.unique_id}" self.doc_id = '7' self.ignore_list = [] def setUp(self): self.set_common_values() - retry_request(delete_index, args=(self.proxy_endpoint, self.index, self.auth), + retry_request(delete_index, args=(self.proxy_endpoint, self.index, self.source_auth, self.source_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) - retry_request(delete_document, args=(self.proxy_endpoint, self.index, self.doc_id, self.auth), + retry_request(delete_document, args=(self.proxy_endpoint, self.index, self.doc_id, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) def tearDown(self): - delete_index(self.proxy_endpoint, self.index, self.auth) - delete_document(self.proxy_endpoint, self.index, self.doc_id, self.auth) + delete_index(self.proxy_endpoint, self.index, self.source_auth, self.source_verify_ssl) + delete_document(self.proxy_endpoint, self.index, self.doc_id, self.source_auth, self.source_verify_ssl) def test_0001_index(self): # This test will verify that an index will be created (then deleted) on the target cluster when one is created @@ -98,25 +127,31 @@ def test_0001_index(self): # replayer. # Creating an index, then asserting that the index was created on both targets. - proxy_response = retry_request(create_index, args=(self.proxy_endpoint, self.index, self.auth), + proxy_response = retry_request(create_index, args=(self.proxy_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(proxy_response.status_code, HTTPStatus.OK) - target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.auth), + target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.target_auth, + self.target_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(target_response.status_code, HTTPStatus.OK) - source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.auth), + source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(source_response.status_code, HTTPStatus.OK) - proxy_response = retry_request(delete_index, args=(self.proxy_endpoint, self.index, self.auth), + proxy_response = retry_request(delete_index, args=(self.proxy_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(proxy_response.status_code, HTTPStatus.OK) - target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.auth), + target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.target_auth, + self.target_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) self.assertEqual(target_response.status_code, HTTPStatus.NOT_FOUND) - source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.auth), + source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) self.assertEqual(source_response.status_code, HTTPStatus.NOT_FOUND) @@ -127,25 +162,31 @@ def test_0002_document(self): # replayer. # Creating an index, then asserting that the index was created on both targets. - proxy_response = retry_request(create_index, args=(self.proxy_endpoint, self.index, self.auth), + proxy_response = retry_request(create_index, args=(self.proxy_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(proxy_response.status_code, HTTPStatus.OK) - target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.auth), + target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.target_auth, + self.target_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(target_response.status_code, HTTPStatus.OK) - source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.auth), + source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(source_response.status_code, HTTPStatus.OK) # Creating a document, then asserting that the document was created on both targets. - proxy_response = create_document(self.proxy_endpoint, self.index, self.doc_id, self.auth) + proxy_response = create_document(self.proxy_endpoint, self.index, self.doc_id, self.source_auth, + self.source_verify_ssl) self.assertEqual(proxy_response.status_code, HTTPStatus.CREATED) - source_response = get_document(self.source_endpoint, self.index, self.doc_id, self.auth) + source_response = get_document(self.source_endpoint, self.index, self.doc_id, self.source_auth, + self.source_verify_ssl) self.assertEqual(source_response.status_code, HTTPStatus.OK) - target_response = retry_request(get_document, args=(self.target_endpoint, self.index, self.doc_id, self.auth), + target_response = retry_request(get_document, args=(self.target_endpoint, self.index, self.doc_id, + self.target_auth, self.target_verify_ssl), expected_status_code=HTTPStatus.OK) self.assertEqual(target_response.status_code, HTTPStatus.OK) @@ -157,28 +198,33 @@ def test_0002_document(self): self.assertEqual(source_content, target_content) # Deleting the document that was created then asserting that it was deleted on both targets. - proxy_response = delete_document(self.proxy_endpoint, self.index, self.doc_id, self.auth) + proxy_response = delete_document(self.proxy_endpoint, self.index, self.doc_id, self.source_auth, + self.source_verify_ssl) self.assertEqual(proxy_response.status_code, HTTPStatus.OK) - target_response = retry_request(get_document, args=(self.target_endpoint, self.index, self.doc_id, self.auth), + target_response = retry_request(get_document, args=(self.target_endpoint, self.index, self.doc_id, + self.target_auth, self.target_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) self.assertEqual(target_response.status_code, HTTPStatus.NOT_FOUND) - source_response = retry_request(get_document, args=(self.source_endpoint, self.index, self.doc_id, self.auth), + source_response = retry_request(get_document, args=(self.source_endpoint, self.index, self.doc_id, + self.source_auth, self.source_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) self.assertEqual(source_response.status_code, HTTPStatus.NOT_FOUND) # Deleting the index that was created then asserting that it was deleted on both targets. - proxy_response = delete_index(self.proxy_endpoint, self.index, self.auth) + proxy_response = delete_index(self.proxy_endpoint, self.index, self.source_auth, self.source_verify_ssl) self.assertEqual(proxy_response.status_code, HTTPStatus.OK) - target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.auth), + target_response = retry_request(check_index, args=(self.target_endpoint, self.index, self.target_auth, + self.target_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) self.assertEqual(target_response.status_code, HTTPStatus.NOT_FOUND) - source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.auth), + source_response = retry_request(check_index, args=(self.source_endpoint, self.index, self.source_auth, + self.source_verify_ssl), expected_status_code=HTTPStatus.NOT_FOUND) self.assertEqual(source_response.status_code, HTTPStatus.NOT_FOUND) - def test_0004_negativeAuth_invalidCreds(self): + def test_0003_negativeAuth_invalidCreds(self): # This test sends negative credentials to the clusters to validate that unauthorized access is prevented. alphabet = string.ascii_letters + string.digits for _ in range(10): @@ -187,49 +233,57 @@ def test_0004_negativeAuth_invalidCreds(self): credentials = [ (username, password), - (self.username, password), - (username, self.password) + (self.source_username, password), + (username, self.source_password) ] for user, pw in credentials: - response = requests.get(self.proxy_endpoint, auth=(user, pw), verify=False) + response = requests.get(self.proxy_endpoint, auth=(user, pw), verify=self.source_verify_ssl) self.assertEqual(response.status_code, HTTPStatus.UNAUTHORIZED) - def test_0005_negativeAuth_missingCreds(self): + def test_0004_negativeAuth_missingCreds(self): # This test will use no credentials at all # With an empty authorization header - response = requests.get(self.proxy_endpoint, auth=('', ''), verify=False) + response = requests.get(self.proxy_endpoint, auth=('', ''), verify=self.source_verify_ssl) self.assertEqual(response.status_code, HTTPStatus.UNAUTHORIZED) # Without an authorization header. - response = requests.get(self.proxy_endpoint, verify=False) + response = requests.get(self.proxy_endpoint, verify=self.source_verify_ssl) self.assertEqual(response.status_code, HTTPStatus.UNAUTHORIZED) - def test_0006_invalidIncorrectUri(self): + def test_0005_invalidIncorrectUri(self): # This test will send an invalid URI invalidUri = "/invalidURI" - response = requests.get(f'{self.proxy_endpoint}{invalidUri}', auth=self.auth, verify=False) + response = requests.get(f'{self.proxy_endpoint}{invalidUri}', auth=self.source_auth, + verify=self.source_verify_ssl) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) # This test will send an incorrect URI incorrectUri = "/_cluster/incorrectUri" - response = requests.get(f'{self.proxy_endpoint}{incorrectUri}', auth=self.auth, verify=False) + response = requests.get(f'{self.proxy_endpoint}{incorrectUri}', auth=self.source_auth, + verify=self.source_verify_ssl) self.assertEqual(response.status_code, HTTPStatus.METHOD_NOT_ALLOWED) - def test_0007_OSB(self): - cmd = ['docker', 'ps', '--format="{{.ID}}"', '--filter', 'name=migration'] - container_id = subprocess.run(cmd, stdout=subprocess.PIPE, text=True).stdout.strip().replace('"', '') - - if container_id: - cmd_exec = f"docker exec {container_id} ./runTestBenchmarks.sh" - logger.warning(f"Running command: {cmd_exec}") + def test_0006_OSB(self): + if self.deployment_type == "cloud": + cmd_exec = f"/root/runTestBenchmarks.sh --unique-id {self.unique_id}" + logger.warning(f"Running local command: {cmd_exec}") subprocess.run(cmd_exec, shell=True) else: - logger.error("Migration-console container was not found, please double check that deployment was a success") - self.assert_(False) + cmd = ['docker', 'ps', '--format="{{.ID}}"', '--filter', 'name=migration'] + container_id = subprocess.run(cmd, stdout=subprocess.PIPE, text=True).stdout.strip().replace('"', '') + + if container_id: + cmd_exec = f"docker exec {container_id} ./runTestBenchmarks.sh" + logger.warning(f"Running command: {cmd_exec}") + subprocess.run(cmd_exec, shell=True) + else: + logger.error("Migration-console container was not found," + " please double check that deployment was a success") + self.assert_(False) - source_indices = get_indices(self.source_endpoint, self.auth) - target_indices = get_indices(self.target_endpoint, self.auth) + source_indices = get_indices(self.source_endpoint, self.source_auth, self.source_verify_ssl) + target_indices = get_indices(self.target_endpoint, self.target_auth, self.target_verify_ssl) common_indices = set(source_indices) & set(target_indices) valid_indices = [index for index in common_indices if index not in self.ignore_list and index != "searchguard"] @@ -237,8 +291,8 @@ def test_0007_OSB(self): self.assertTrue(valid_indices, "No valid indices found to compare after running OpenSearch Benchmark") for index in valid_indices: - source_count = get_doc_count(self.source_endpoint, index, self.auth) - target_count = get_doc_count(self.target_endpoint, index, self.auth) + source_count = get_doc_count(self.source_endpoint, index, self.source_auth, self.source_verify_ssl) + target_count = get_doc_count(self.target_endpoint, index, self.target_auth, self.target_verify_ssl) if source_count != target_count: logger.error(f'{index}: doc counts do not match - Source = {source_count}, Target = {target_count}')