Skip to content

Commit

Permalink
Merge pull request Azure#2 from mbhardwaj-msft/mbhardwaj/azload-20241205
Browse files Browse the repository at this point in the history
[load] dashboard reports | debug mode | copy artifacts sas url
  • Loading branch information
mbhardwaj-msft authored Dec 9, 2024
2 parents 8a1e1ec + f725e14 commit d63c746
Show file tree
Hide file tree
Showing 41 changed files with 292,665 additions and 12,524 deletions.
3 changes: 3 additions & 0 deletions src/load/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Release History
* Add support for multi-region load test configuration. Multi-region load test configuration can be set using `--regionwise-engines` argument in 'az load test create' and 'az load test update' commands. Multi-region load test configuration set in YAML config file under key `regionalLoadTestConfig` will also be honoured.
* Bug fix for `engineInstances` being reset to 1 and not getting backfilled using test's existing configuration when engine instances are not explicitly specified either in YAML config file or CLI argument.
* Add support for advanced URL test with multiple HTTP request using JSON file. Add `--test-type` argument to 'az load test create' and honor `testType` key in YAML config file.
* Add CLI parameter `--report` to 'az load test-run download-files' to download the dashboard reports.
* Enable debug level logging using `--debug-mode` argument in 'az load test-run create' command .
* Return the SAS URL to copy artifacts to storage accounts using command 'az load test-run copy-artifacts-url'.


1.3.1
Expand Down
1 change: 1 addition & 0 deletions src/load/azext_load/data_plane/load_test_run/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def load_test_run_commands(self, _):
validator=validators.validate_download,
)
g.custom_command("stop", "stop_test_run", confirmation=True)
g.custom_command("copy-artifacts-url", "copy_test_run_artifacts_url")

with self.command_group(
"load test-run app-component",
Expand Down
174 changes: 92 additions & 82 deletions src/load/azext_load/data_plane/load_test_run/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import os
from azext_load.data_plane.utils.utils import (
create_or_update_test_run_body,
download_file,
get_file_info_and_download,
get_testrun_data_plane_client,
)
from azure.cli.core.azclierror import InvalidArgumentValueError
Expand All @@ -29,6 +28,7 @@ def create_test_run(
certificate=None,
resource_group_name=None,
no_wait=False,
debug_mode=False,
):
logger.info("Create test run started")
client = get_testrun_data_plane_client(cmd, load_test_resource, resource_group_name)
Expand All @@ -49,6 +49,7 @@ def create_test_run(
env=env,
secrets=secrets,
certificate=certificate,
debug_mode=debug_mode,
)
logger.debug("Creating test run with following request %s", test_run_body)
poller = client.begin_test_run(
Expand Down Expand Up @@ -132,6 +133,86 @@ def stop_test_run(cmd, load_test_resource, test_run_id, resource_group_name=None
return response


def copy_test_run_artifacts_url(cmd, load_test_resource, test_run_id, resource_group_name=None):
client = get_testrun_data_plane_client(cmd, load_test_resource, resource_group_name)
logger.info("Fetching test run copy artifacts SAS URL for test run %s", test_run_id)
test_run_data = client.get_test_run(test_run_id=test_run_id)
artifacts_container = test_run_data.get(
"testArtifacts", {}).get(
"outputArtifacts", {}).get(
"artifactsContainerInfo")
if artifacts_container is None:
logger.warning("No test artifacts container found for test run %s", test_run_id)
else:
logger.info("Fetched test run copy artifacts SAS URL %s", artifacts_container)
return artifacts_container.get("url")


def _download_results_file(test_run_output_artifacts, test_run_id, path):
logger.info("Downloading results file for test run %s", test_run_id)
if test_run_output_artifacts is not None:
result_file_info = test_run_output_artifacts.get("resultFileInfo")
if result_file_info is not None and result_file_info.get("url") is not None:
file_path = get_file_info_and_download(result_file_info, path)
logger.warning("Results file downloaded to %s", file_path)
else:
logger.info("No results file found for test run %s", test_run_id)
else:
logger.warning(
"No results file found for test run %s",
test_run_id,
)


def _download_reports_file(test_run_output_artifacts, test_run_id, path):
logger.info("Downloading report file for test run %s", test_run_id)
if test_run_output_artifacts is not None:
report_file_info = test_run_output_artifacts.get("reportFileInfo")
if report_file_info is not None and report_file_info.get("url") is not None:
file_path = get_file_info_and_download(report_file_info, path)
logger.warning("Report file downloaded to %s", file_path)
else:
logger.info("No report file found for test run %s", test_run_id)
else:
logger.warning(
"No report file found for test run %s",
test_run_id,
)


def _download_logs_file(test_run_output_artifacts, test_run_id, path):
logger.info("Downloading log file for test run %s", test_run_id)
if test_run_output_artifacts is not None:
logs_file_info = test_run_output_artifacts.get("logsFileInfo")
if logs_file_info is not None and logs_file_info.get("url") is not None:
file_path = get_file_info_and_download(logs_file_info, path)
logger.warning("Log file downloaded to %s", file_path)
else:
logger.info("No log file found for test run %s", test_run_id)
else:
logger.warning(
"No results file and output artifacts found for test run %s",
test_run_id,
)


def _download_input_file(test_run_input_artifacts, test_run_id, path):
logger.info("Downloading input artifacts for test run %s", test_run_id)
if test_run_input_artifacts is not None:
files_to_download = []
for item in test_run_input_artifacts.values():
if isinstance(item, list):
files_to_download.extend(item)
else:
files_to_download.append(item)
for artifact_data in files_to_download:
if artifact_data.get("url") is not None:
get_file_info_and_download(artifact_data, path)
logger.warning("Input artifacts downloaded to %s", path)
else:
logger.warning("No input artifacts found for test run %s", test_run_id)


def download_test_run_files(
cmd,
load_test_resource,
Expand All @@ -140,6 +221,7 @@ def download_test_run_files(
test_run_input=False,
test_run_log=False,
test_run_results=False,
test_run_report=False,
resource_group_name=None,
force=False, # pylint: disable=unused-argument
):
Expand All @@ -149,91 +231,19 @@ def download_test_run_files(
if test_run_data.get("testArtifacts") is None:
logger.warning("No test artifacts found for test run %s", test_run_id)

test_run_input_artifacts = test_run_data.get("testArtifacts", {}).get("inputArtifacts")
test_run_output_artifacts = test_run_data.get("testArtifacts", {}).get("outputArtifacts")
if test_run_input:
logger.info("Downloading input artifacts for test run %s", test_run_id)
if test_run_data.get("testArtifacts", {}).get("inputArtifacts") is not None:
input_artifacts = test_run_data.get("testArtifacts", {}).get(
"inputArtifacts", {}
)
files_to_download = []
for item in input_artifacts.values():
if isinstance(item, list):
files_to_download.extend(item)
else:
files_to_download.append(item)
for artifact_data in files_to_download:
if artifact_data.get("url") is not None:
url = artifact_data.get("url")
file_name = artifact_data.get("fileName")
file_path = os.path.join(path, file_name)
download_file(url, file_path)
logger.warning("Input artifacts downloaded to %s", path)
else:
logger.warning("No input artifacts found for test run %s", test_run_id)
_download_input_file(test_run_input_artifacts, test_run_id, path)

if test_run_log:
logger.info("Downloading log file for test run %s", test_run_id)
if test_run_data.get("testArtifacts", {}).get("outputArtifacts") is not None:
if (
test_run_data.get("testArtifacts", {})
.get("outputArtifacts", {})
.get("logsFileInfo")
is not None
):
url = (
test_run_data.get("testArtifacts", {})
.get("outputArtifacts", {})
.get("logsFileInfo")
.get("url")
)
file_name = (
test_run_data.get("testArtifacts", {})
.get("outputArtifacts", {})
.get("logsFileInfo", {})
.get("fileName")
)
file_path = os.path.join(path, file_name)
download_file(url, file_path)
logger.warning("Log file downloaded to %s", file_path)
else:
logger.info("No log file found for test run %s", test_run_id)
else:
logger.warning(
"No results file and output artifacts found for test run %s",
test_run_id,
)
_download_logs_file(test_run_output_artifacts, test_run_id, path)

if test_run_results:
logger.info("Downloading results file for test run %s", test_run_id)
if test_run_data.get("testArtifacts", {}).get("outputArtifacts") is not None:
if (
test_run_data.get("testArtifacts", {})
.get("outputArtifacts", {})
.get("resultFileInfo")
is not None
):
url = (
test_run_data.get("testArtifacts", {})
.get("outputArtifacts", {})
.get("resultFileInfo")
.get("url")
)
file_name = (
test_run_data.get("testArtifacts", {})
.get("outputArtifacts", {})
.get("resultFileInfo", {})
.get("fileName")
)
file_path = os.path.join(path, file_name)
download_file(url, file_path)
logger.warning("Results file downloaded to %s", file_path)
else:
logger.info("No results file found for test run %s", test_run_id)
else:
logger.warning(
"No results file found for test run %s",
test_run_id,
)
_download_results_file(test_run_output_artifacts, test_run_id, path)

if test_run_report:
_download_reports_file(test_run_output_artifacts, test_run_id, path)


# app components
Expand Down
18 changes: 16 additions & 2 deletions src/load/azext_load/data_plane/load_test_run/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
- name: Rerun an existing test run.
text: |
az load test-run create --load-test-resource sample-alt-resource --resource-group sample-rg --test-id sample-test-id --test-run-id sample-test-run-id --description "Test run description" --existing-test-run-id existing_test_run_id
- name: Create a test run with debug level logging enabled.
text: |
az load test-run create --load-test-resource sample-alt-resource --resource-group sample-rg --test-id sample-test-id --test-run-id sample-test-run-id --debug-mode
"""

helps[
Expand Down Expand Up @@ -79,15 +82,26 @@
az load test-run delete --load-test-resource sample-alt-resource --resource-group sample-rg --test-run-id sample-test-run-id --yes
"""

helps[
"load test-run copy-artifacts-url"
] = """
type: command
short-summary: Return the SAS URL to copy artifacts to storage accounts.
examples:
- name: Fetch SAS URL.
text: |
az load test-run copy-artifacts-url --load-test-resource sample-alt-resource --resource-group sample-rg --test-run-id sample-test-run-id
"""

helps[
"load test-run download-files"
] = """
type: command
short-summary: Download files for an existing load test run.
examples:
- name: Download input, log and result files for a test run. The directory should already exist.
- name: Download input, log, result and dashboard report files for a test run. The directory should already exist.
text: |
az load test-run download-files --load-test-resource sample-alt-resource --resource-group sample-rg --test-run-id sample-test-run-id --path ~/Downloads/OutputArtifacts --input --log --result
az load test-run download-files --load-test-resource sample-alt-resource --resource-group sample-rg --test-run-id sample-test-run-id --path ~/Downloads/OutputArtifacts --input --log --result --report
- name: Download input and log files for a test run by creating the directory if it does not exist.
text: |
az load test-run download-files --load-test-resource sample-alt-resource --resource-group sample-rg --test-run-id sample-test-run-id --path ~/Downloads/OutputArtifacts --input --log --force
Expand Down
2 changes: 2 additions & 0 deletions src/load/azext_load/data_plane/load_test_run/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def load_arguments(self, _):
c.argument("env", argtypes.env)
c.argument("secrets", argtypes.secret)
c.argument("certificate", argtypes.certificate)
c.argument("debug_mode", argtypes.test_run_debug_mode)

with self.argument_context("load test-run update") as c:
c.argument("test_id", argtypes.test_id)
Expand All @@ -31,6 +32,7 @@ def load_arguments(self, _):
c.argument("test_run_input", argtypes.test_run_input)
c.argument("test_run_log", argtypes.test_run_log)
c.argument("test_run_results", argtypes.test_run_results)
c.argument("test_run_report", argtypes.test_run_report)
c.argument("force", argtypes.force)

with self.argument_context("load test-run list") as c:
Expand Down
14 changes: 14 additions & 0 deletions src/load/azext_load/data_plane/utils/argtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@
+ quote_text.format("certificate"),
)

test_run_debug_mode = CLIArgumentType(
options_list=["--debug-mode"],
action="store_true",
default=False,
help="Enable debug level logging for the test run.",
)

dir_path = CLIArgumentType(
validator=validators.validate_dir_path,
options_list=["--path"],
Expand Down Expand Up @@ -235,6 +242,13 @@
help="Download the results files zip.",
)

test_run_report = CLIArgumentType(
options_list=["--report"],
action="store_true",
default=False,
help="Download the dashboard report files zip.",
)

app_component_id = CLIArgumentType(
validator=validators.validate_app_component_id,
options_list=["--app-component-id"],
Expand Down
11 changes: 11 additions & 0 deletions src/load/azext_load/data_plane/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ def get_enum_values(enum):
return [item.value for item in enum]


def get_file_info_and_download(file_info, path):
url = file_info.get("url")
file_name = file_info.get("fileName")
file_path = os.path.join(path, file_name)
download_file(url, file_path)
return file_path


def download_file(url, file_path):
logger.debug("Downloading file started")
response = None
Expand Down Expand Up @@ -634,6 +642,7 @@ def create_or_update_test_run_body(
env=None,
secrets=None,
certificate=None,
debug_mode=None,
):
logger.info("Creating a request body for create test run")
new_body = {"testId": test_id}
Expand All @@ -647,6 +656,8 @@ def create_or_update_test_run_body(
new_body["secrets"] = secrets
if certificate is not None:
new_body["certificate"] = certificate
if debug_mode is not None:
new_body["debugLogsEnabled"] = debug_mode
logger.debug("Request body for create test run: %s", new_body)
return new_body

Expand Down
6 changes: 6 additions & 0 deletions src/load/azext_load/tests/latest/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class LoadConstants:
AUTOSTOP_ERROR_RATE_INTEGER = 75
AUTOSTOP_ERROR_RATE_TIME_WINDOW = 90

FLOAT_TOLERANCE = 1e-6

class LoadTestConstants(LoadConstants):
# Test IDs for load test commands
Expand Down Expand Up @@ -168,6 +169,8 @@ class LoadTestRunConstants(LoadConstants):
APP_COMPONENT_TEST_ID = "app-component-test-case"
SERVER_METRIC_TEST_ID = "server-metric-test-case"
METRIC_TEST_ID = "metric-test-case"
DEBUG_MODE_TEST_ID = "debug-mode-test-case"
SAS_URL_TEST_ID = "sas-url-test-case"

# Test Run IDs for load test run commands
CREATE_TEST_RUN_ID = "create-test-run-case"
Expand All @@ -181,6 +184,9 @@ class LoadTestRunConstants(LoadConstants):
DOWNLOAD_TEST_RUN_ID = "download-test-run-case"
APP_COMPONENT_TEST_RUN_ID = "app-component-test-run-case"
INVALID_TEST_RUN_ID = r"A$%invalid-testrun-case-testrunid"
DEBUG_MODE_TEST_RUN_ID = "debug-mode-test-run-case"
SAS_URL_TEST_RUN_ID = "sas-url-test-run-case"
SAS_URL_TEST_RUN_ID_1 = "sas-url-test-run-case-1"

DESCRIPTION = r"Sample_testrun_description"
DISPLAY_NAME = r"Sample_testrun_display_name"
Loading

0 comments on commit d63c746

Please sign in to comment.