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

Adds PerformanceTestSuiteClass and PerformanceTestCluster for Performance Testing #314

Merged
merged 28 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
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
46 changes: 46 additions & 0 deletions bundle-workflow/src/perf_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import argparse
import os

import yaml

from git.git_repository import GitRepository
from manifests.bundle_manifest import BundleManifest
from system.working_directory import WorkingDirectory
from test_workflow.perf_test_cluster import Cluster
from test_workflow.perf_test_suite import PerfTestSuite

"""
Entry point for Performance Test with bundle manifest, config file containing the required arguments for running
rally test and the stack name for the cluster. Will call out in test.sh with perf as argument
"""

parser = argparse.ArgumentParser(description="Test an OpenSearch Bundle")
Copy link
Member

Choose a reason for hiding this comment

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

Please add comments for how structure of this file should work since it doesn't follow the typical interface/inheritance pattern

Copy link
Member Author

@owaiskazi19 owaiskazi19 Sep 2, 2021

Choose a reason for hiding this comment

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

I have added the comment and explained the required arguments for Perf Test.

parser.add_argument('--bundle-manifest', type=argparse.FileType('r'), help="Bundle Manifest file.")
parser.add_argument('--stack', dest='stack', help='Stack name for performance test')
parser.add_argument('--config', type=argparse.FileType('r'), help="Config file.")
args = parser.parse_args()

manifest = BundleManifest.from_file(args.bundle_manifest)

config = yaml.load(args.config, Loader=yaml.FullLoader)


def get_infra_repo_url():
if "GITHUB_TOKEN" in os.environ:
return "https://${GITHUB_TOKEN}@github.com/opensearch-project/opensearch-infra.git"
return "https://github.com/opensearch-project/opensearch-infra.git"


current_workspace = os.path.join(os.getcwd(), 'infra')
cloned_repo = GitRepository(get_infra_repo_url(), 'main', current_workspace)
security = False
for component in manifest.components:
if component.name == 'security':
security = True

with WorkingDirectory(current_workspace) as curdir:
with Cluster.create(manifest, config, args.stack, security) as test_cluster_endpoint:

os.chdir(current_workspace)
perf_test_suite = PerfTestSuite(manifest, test_cluster_endpoint, security, current_workspace)
perf_test_suite.execute()
Copy link
Member

Choose a reason for hiding this comment

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

@saratvemulapalli This will be nice and easy with a base class with a prepare and execute.

17 changes: 17 additions & 0 deletions bundle-workflow/src/system/working_directory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os
from contextlib import contextmanager


@contextmanager
def WorkingDirectory(path):
try:
saved_path = os.getcwd()
yield os.chdir(path)
finally:
os.chdir(saved_path)
74 changes: 74 additions & 0 deletions bundle-workflow/src/test_workflow/perf_test_cluster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import json
import os
import subprocess
from contextlib import contextmanager

from test_workflow.test_cluster import TestCluster


class Cluster:
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 be a capability of any cluster, so PerfTestCluster would inherit TestCluster and the latter has a static def create(cls, ...) method that is implemented by calling cls._init_...

You can refactor this later.

@contextmanager
def create(manifest, config, stack_name, security):
perf_test_cluster = PerfTestCluster(manifest, config, stack_name, security)
try:
perf_test_cluster.create()
yield perf_test_cluster.endpoint()
finally:
perf_test_cluster.destroy()


class PerfTestCluster(TestCluster):
"""
Represents a performance test cluster. This class deploys the opensearch bundle with CDK and returns the private IP.
"""

def __init__(self, bundle_manifest, config, stack_name, security):
self.manifest = bundle_manifest
self.work_dir = 'tools/cdk/mensor/single-node/'
self.stack_name = stack_name
self.cluster_endpoint = None
self.cluster_port = None
self.output_file = 'output.json'
self.ip_address = None
self.security = 'enable' if security else 'disable'
role = config['Constants']['Role']
params_dict = {
'url': self.manifest.build.location,
'security_group_id': config['Constants']['SecurityGroupId'],
'vpc_id': config['Constants']['VpcId'],
'account_id': config['Constants']['AccountId'],
'region': config['Constants']['Region'],
'stack_name': self.stack_name,
'security': self.security,
'architecture': self.manifest.build.architecture,
}
params_list = []
for key, value in params_dict.items():
params_list.append(f' -c {key}={value}')
role_params = f' --require-approval=never --plugin cdk-assume-role-credential-plugin'\
f' -c assume-role-credentials:writeIamRoleName={role} -c assume-role-credentials:readIamRoleName={role} '
self.params = ''.join(params_list) + role_params

def create(self):
os.chdir(self.work_dir)
command = f'cdk deploy {self.params} --outputs-file {self.output_file}'
print(f'Executing "{command}" in {os.getcwd()}')
subprocess.check_call(command, cwd=os.getcwd(), shell=True)
with open(self.output_file, 'r') as read_file:
load_output = json.load(read_file)
self.ip_address = load_output[self.stack_name]['PrivateIp']
print('Private IP:', self.ip_address)

def endpoint(self):
Copy link
Member

Choose a reason for hiding this comment

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

Assign self.endpoint = when you have it so you can use a property.

Copy link
Member Author

Choose a reason for hiding this comment

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

Assigned self.cluster_endpoint.

self.cluster_endpoint = self.ip_address
return self.cluster_endpoint

def port(self):
Copy link
Member

Choose a reason for hiding this comment

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

Assign, same as above.

Copy link
Member Author

Choose a reason for hiding this comment

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

Assigned self.cluster_port

self.cluster_port = 443 if self.security == 'enable' else 9200
return self.cluster_port

def destroy(self):
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Are we talking about destroying multiple clusters here? May be I did't get this part

Copy link
Member

Choose a reason for hiding this comment

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

I clarified in a comment above. You don't want to rely on the caller to always do a finally block that calls .destroy(). If the caller were to forget, you'd end up with a running cluster. But if you adopt the with ... pattern, it would be taken care of by the garbage collector in the worst case scenario.

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented a yield version

os.chdir(self.work_dir)
command = f'cdk destroy {self.params} --force'
print(f'Executing "{command}" in {os.getcwd()}')
subprocess.check_call(command, cwd=os.getcwd(), shell=True)
33 changes: 33 additions & 0 deletions bundle-workflow/src/test_workflow/perf_test_suite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os
import subprocess

from system.working_directory import WorkingDirectory


class PerfTestSuite:
"""
Represents a performance test suite. This class runs rally test on the deployed cluster with the provided IP.
"""

def __init__(self, bundle_manifest, endpoint, security, current_workspace):
self.manifest = bundle_manifest
self.work_dir = 'tools/cdk/mensor/mensor_tests'
self.endpoint = endpoint
self.security = security
self.current_workspace = current_workspace
self.command = f'pipenv run python test_config.py -i {self.endpoint} -b {self.manifest.build.id}'\
f' -a {self.manifest.build.architecture} '

def execute(self):
try:
with WorkingDirectory(self.work_dir):
dir = os.getcwd()
subprocess.check_call('python3 -m pipenv install', cwd=dir, shell=True)
subprocess.check_call('pipenv install', cwd=dir, shell=True)

if self.security:
subprocess.check_call(f'{self.command} -s', cwd=dir, shell=True)
else:
subprocess.check_call(f'{self.command}', cwd=dir, shell=True)
finally:
os.chdir(self.current_workspace)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
build:
Copy link
Member

@gaiksaya gaiksaya Sep 3, 2021

Choose a reason for hiding this comment

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

This is not test_manifest.yaml. Its bundle_manifest.yml, Rename it to avoid confusion. Here is the test-manifest.yml

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll address it in the next PR. Thanks

Copy link
Member Author

Choose a reason for hiding this comment

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

Addressed in #404

architecture: x64
id: 41d5ae25183d4e699e92debfbe3f83bd
location: https://artifacts.opensearch.org/bundles/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/opensearch-1.0.0-linux-x64.tar.gz
name: OpenSearch
version: 1.0.0
components:
- commit_id: fb25458f38c30a7ab06de21b0068f1fe3ad56134
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/bundle/opensearch-min-1.0.0-linux-x64.tar.gz
name: OpenSearch
ref: 1.0
repository: https://github.com/saratvemulapalli/OpenSearch.git
- commit_id: 7fad9529358259de529763c1c923fd947817a3bd
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-job-scheduler-1.0.0.0.zip
name: job-scheduler
ref: 1.0.0.0
repository: https://github.com/opensearch-project/job-scheduler.git
- commit_id: 65bb94fb7d46a88b07b61622585ed701918b19c5
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-sql-1.0.0.0.zip
name: sql
ref: 1.0.0.0
repository: https://github.com/opensearch-project/sql.git
- commit_id: a14ccd49389ca41446acc3200e3e870cde15a68e
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-alerting-1.0.0.0.zip
name: alerting
ref: 1.0.0.0
repository: https://github.com/opensearch-project/alerting.git
- commit_id: 2e21d59749526baa8e4666168643c4594cdadf79
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-security-1.0.0.0.zip
name: security
ref: 1.0.0.0
repository: https://github.com/opensearch-project/security.git
- commit_id: 091fe9f6612cd7e85054918036587bcd3c67eab1
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-index-management-1.0.0.0.zip
name: index-management
ref: 1.0.0.0
repository: https://github.com/opensearch-project/index-management.git
- commit_id: 9b29a99b05f2c8cd9d54dc868994cab8460ff0db
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-knn-1.0.0.0.zip
name: k-NN
ref: 1.0.0.0
repository: https://github.com/opensearch-project/k-NN.git
- commit_id: 502c96e54fae1cec9fee1fafd77ad92fde9d2459
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-anomaly-detection-1.0.0.0.zip
name: anomaly-detection
ref: 1.0
repository: https://github.com/opensearch-project/anomaly-detection.git
- commit_id: bd31e80adf6d52c1b4662d0d2cc9b30d8ae14309
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-asynchronous-search-1.0.0.0.zip
name: asynchronous-search
ref: main
repository: https://github.com/opensearch-project/asynchronous-search.git
- commit_id: 72705e2dfcad760c5de7609891700aa11d767884
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-reports-scheduler-1.0.0.0.zip
name: dashboards-reports
ref: 1.0.0.0
repository: https://github.com/opensearch-project/dashboards-reports.git
- commit_id: fd745a77c19df4991254b495cf0ec3730c66534d
location: https://artifacts.opensearch.org/builds/1.0.0/41d5ae25183d4e699e92debfbe3f83bd/plugins/opensearch-notebooks-1.0.0.0.zip
name: dashboards-notebooks
ref: 1.0.0.0
repository: https://github.com/opensearch-project/dashboards-notebooks.git
schema-version: '1.0'
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os
import unittest
from unittest.mock import MagicMock, patch

from manifests.bundle_manifest import BundleManifest
from test_workflow.perf_test_cluster import PerfTestCluster


class TestPerfCluster(unittest.TestCase):
def setUp(self):
self.data_path = os.path.realpath(
os.path.join(os.path.dirname(__file__), "data")
)
self.manifest_filename = os.path.join(
self.data_path, "test_manifest.yaml"
)
self.manifest = BundleManifest.from_path(self.manifest_filename)
self.stack_name = 'stack'
self.security = 'disable'
config = {
'Constants': {
'SecurityGroupId': 'sg-00000000',
'VpcId': 'vpc-12345',
'AccountId': '12345678',
'Region': 'us-west-2',
'Role': 'role-arn'
}
}
self.perf_test_cluster = PerfTestCluster(
bundle_manifest=self.manifest, config=config, stack_name=self.stack_name, security=self.security
)

def test_create(self):
mock_file = MagicMock(side_effect=[{"stack": {"PrivateIp": "10.10.10.10"}}])
with patch('test_workflow.perf_test_cluster.os.chdir') as mock_chdir:
with patch("subprocess.check_call") as mock_check_call:
with patch("builtins.open", MagicMock()):
with patch("json.load", mock_file):
self.perf_test_cluster.create()
mock_chdir.assert_called_once_with('tools/cdk/mensor/single-node/')
self.assertEqual(mock_check_call.call_count, 1)

def test_endpoint(self):
self.assertEqual(self.perf_test_cluster.endpoint(), None)

def test_port(self):
self.assertEqual(self.perf_test_cluster.port(), 443)

def test_destroy(self):
with patch('test_workflow.perf_test_cluster.os.chdir') as mock_chdir:
with patch("subprocess.check_call") as mock_check_call:
self.perf_test_cluster.destroy()
mock_chdir.assert_called_once_with('tools/cdk/mensor/single-node/')
self.assertEqual(mock_check_call.call_count, 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

import os
import unittest
from unittest.mock import patch

from manifests.bundle_manifest import BundleManifest
from test_workflow.perf_test_suite import PerfTestSuite


class TestPerfSuite(unittest.TestCase):
def setUp(self):
os.chdir(os.path.dirname(__file__))
self.manifest = BundleManifest.from_path("data/test_manifest.yaml")
self.endpoint = None
self.perf_test_suite = PerfTestSuite(
bundle_manifest=self.manifest, endpoint=None, security=False, current_workspace='current_workspace'
)

def test_execute(self):
with patch('test_workflow.perf_test_suite.os.chdir'):
with patch("subprocess.check_call") as mock_check_call:
self.perf_test_suite.execute()
self.assertEqual(mock_check_call.call_count, 3)