Skip to content

Commit f78d9f6

Browse files
authored
[load] Adding CLI support for engine reference identity feature (#8492)
* updated sdk with 2024-12-01 version api * Updating client stop call and response from list_metric_dimension_values * Fixi for few regression issue * Updated test recordings to use new API version for data plane * Changes for azdev style * initial params setup * Added validators and support for config input * Added test cases for engine mi feature * Updated history and setup file * Addressing PR comments * Fixing naming of argument * test recordings update * Addressing PR comments * Updating version to 1.6.0
1 parent 9270aae commit f78d9f6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+10670
-8948
lines changed

src/load/HISTORY.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Release History
44
===============
55

66

7+
1.6.0
8+
++++++
9+
* Add support for engine reference identity using CLI. Engine reference identity can be set using `--engine-ref-id-type` and `--engine-ref-ids` argument in 'az load test create' and 'az load test update' commands. Engine reference identity set in YAML config file under key `referenceIdentities` with `kind` as `Engine` will also be honoured.
10+
11+
712
1.5.0
813
++++++
914
* Add support for Locust based load tests.

src/load/azext_load/data_plane/load_test/custom.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def create_test(
5656
autostop_error_rate=None,
5757
autostop_error_rate_time_window=None,
5858
regionwise_engines=None,
59+
engine_ref_id_type=None,
60+
engine_ref_ids=None,
5961
):
6062
client = get_admin_data_plane_client(cmd, load_test_resource, resource_group_name)
6163
logger.info("Create test has started for test ID : %s", test_id)
@@ -91,6 +93,8 @@ def create_test(
9193
disable_public_ip=disable_public_ip,
9294
autostop_criteria=autostop_criteria,
9395
regionwise_engines=regionwise_engines,
96+
engine_ref_id_type=engine_ref_id_type,
97+
engine_ref_ids=engine_ref_ids,
9498
)
9599
else:
96100
yaml = load_yaml(load_test_config_file)
@@ -119,6 +123,8 @@ def create_test(
119123
disable_public_ip=disable_public_ip,
120124
autostop_criteria=autostop_criteria,
121125
regionwise_engines=regionwise_engines,
126+
engine_ref_id_type=engine_ref_id_type,
127+
engine_ref_ids=engine_ref_ids,
122128
)
123129
logger.debug("Creating test with test ID: %s and body : %s", test_id, body)
124130
response = client.create_or_update_test(test_id=test_id, body=body)
@@ -158,6 +164,8 @@ def update_test(
158164
autostop_error_rate=None,
159165
autostop_error_rate_time_window=None,
160166
regionwise_engines=None,
167+
engine_ref_id_type=None,
168+
engine_ref_ids=None,
161169
):
162170
client = get_admin_data_plane_client(cmd, load_test_resource, resource_group_name)
163171
logger.info("Update test has started for test ID : %s", test_id)
@@ -191,6 +199,8 @@ def update_test(
191199
disable_public_ip=disable_public_ip,
192200
autostop_criteria=autostop_criteria,
193201
regionwise_engines=regionwise_engines,
202+
engine_ref_id_type=engine_ref_id_type,
203+
engine_ref_ids=engine_ref_ids,
194204
)
195205
else:
196206
body = create_or_update_test_without_config(
@@ -208,6 +218,8 @@ def update_test(
208218
disable_public_ip=disable_public_ip,
209219
autostop_criteria=autostop_criteria,
210220
regionwise_engines=regionwise_engines,
221+
engine_ref_id_type=engine_ref_id_type,
222+
engine_ref_ids=engine_ref_ids
211223
)
212224
logger.info("Updating test with test ID: %s", test_id)
213225
response = client.create_or_update_test(test_id=test_id, body=body)

src/load/azext_load/data_plane/load_test/help.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
az load test create --load-test-resource sample-alt-resource --resource-group sample-rg --test-id sample-test-id --load-test-config-file ~/resources/sample-config.yaml
2020
- name: Create a test with arguments.
2121
text: |
22-
az load test create --test-id sample-test-id --load-test-resource sample-alt-resource --resource-group sample-rg --display-name "Sample Name" --description "Test description" --test-plan sample-jmx.jmx --engine-instances 1 --env rps=2 count=1
22+
az load test create --test-id sample-test-id --load-test-resource sample-alt-resource --resource-group sample-rg --display-name "Sample Name" --description "Test description" --test-plan sample-jmx.jmx --engine-instances 1 --env rps=2 count=1 --engine-ref-id-type SystemAssigned
2323
- name: Create a test with load test config file and override engine-instance and env using arguments and don't wait for file upload.
2424
text: |
2525
az load test create --load-test-resource sample-alt-resource --resource-group sample-rg --test-id sample-test-id --load-test-config-file ~/resources/sample-config.yaml --engine-instances 1 --env rps=2 count=1 --no-wait
@@ -46,6 +46,9 @@
4646
- name: Create a Locust based load test
4747
text: |
4848
az load test create --test-id sample-test-id --load-test-resource sample-alt-resource --resource-group sample-rg --test-plan ~/resources/sample-locust-file.py --test-type Locust --env LOCUST_HOST="https://azure.microsoft.com" LOCUST_SPAWN_RATE=0.3 LOCUST_RUN_TIME=120 LOCUST_USERS=4
49+
- name: Create a test with user assigned Managed Identity reference for engine.
50+
text: |
51+
az load test create --test-id sample-test-id --load-test-resource sample-alt-resource --resource-group sample-rg --display-name "Sample Name" --engine-ref-id-type UserAssigned --engine-ref-ids "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sample-rg/providers/microsoft.managedidentity/userassignedidentities/sample-mi"
4952
"""
5053

5154
helps[
@@ -97,6 +100,9 @@
97100
- name: Update multi-region load configuration.
98101
text: |
99102
az load test update --load-test-resource sample-alt-resource --resource-group sample-rg --test-id sample-existing-test-id --engine-instances 5 --regionwise-engines eastus=2 westus2=1 eastasia=2
103+
- name: Update a test with user assigned Managed Identity reference for engine to SystemAssigned.
104+
text: |
105+
az load test update --test-id sample-test-id --load-test-resource sample-alt-resource --resource-group sample-rg --display-name "Sample Name" --engine-ref-id-type SystemAssigned
100106
"""
101107

102108
helps[

src/load/azext_load/data_plane/load_test/params.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def load_arguments(self, _):
3434
c.argument("autostop_error_rate", argtypes.autostop_error_rate)
3535
c.argument("autostop_error_rate_time_window", argtypes.autostop_error_rate_time_window)
3636
c.argument("regionwise_engines", argtypes.regionwise_engines)
37+
c.argument("engine_ref_id_type", argtypes.engine_ref_id_type)
38+
c.argument("engine_ref_ids", argtypes.engine_ref_ids)
3739

3840
with self.argument_context("load test update") as c:
3941
c.argument("load_test_config_file", argtypes.load_test_config_file)
@@ -55,6 +57,8 @@ def load_arguments(self, _):
5557
c.argument("autostop_error_rate", argtypes.autostop_error_rate)
5658
c.argument("autostop_error_rate_time_window", argtypes.autostop_error_rate_time_window)
5759
c.argument("regionwise_engines", argtypes.regionwise_engines)
60+
c.argument("engine_ref_id_type", argtypes.engine_ref_id_type)
61+
c.argument("engine_ref_ids", argtypes.engine_ref_ids)
5862

5963
with self.argument_context("load test set-baseline") as c:
6064
c.argument("test_run_id", argtypes.test_run_id)

src/load/azext_load/data_plane/utils/argtypes.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,23 @@
392392
help="Specify the engine count for each region in the format: region1=engineCount1 region2=engineCount2 .... Use region names in the format accepted by Azure Resource Manager (ARM). Ensure the regions are supported by Azure Load Testing. Multi-region load tests can only target public endpoints.",
393393
)
394394

395+
engine_ref_id_type = CLIArgumentType(
396+
options_list=["--engine-ref-id-type"],
397+
type=str,
398+
completer=get_generic_completion_list(
399+
utils.get_enum_values(models.EngineIdentityType)
400+
),
401+
choices=utils.get_enum_values(models.EngineIdentityType),
402+
help="Type of identity to be configured for the engine.",
403+
)
404+
405+
engine_ref_ids = CLIArgumentType(
406+
options_list=["--engine-ref-ids"],
407+
nargs="+",
408+
validator=validators.validate_engine_ref_ids,
409+
help="Space separated list of fully qualified resource IDs of the managed identities to be configured on the engine. Required only for user assigned identities. ",
410+
)
411+
395412
response_time_aggregate = CLIArgumentType(
396413
options_list=["--aggregation"],
397414
type=str,

src/load/azext_load/data_plane/utils/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class LoadTestConfigKeys:
2828
REGION = "region"
2929
QUICK_START = "quickStartTest"
3030
SPLIT_CSV = "splitAllCSVs"
31+
REFERENCE_IDENTITIES = "referenceIdentities"
32+
ENGINE = "Engine"
33+
TYPE = "type"
34+
KIND = "kind"
35+
VALUE = "value"
3136

3237

3338
@dataclass

src/load/azext_load/data_plane/utils/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ class IdentityType(str, Enum):
1111
UserAssigned = "UserAssigned"
1212

1313

14+
class EngineIdentityType(str, Enum):
15+
SystemAssigned = "SystemAssigned"
16+
UserAssigned = "UserAssigned"
17+
NoneValue = "None"
18+
19+
1420
class AllowedFileTypes(str, Enum):
1521
ADDITIONAL_ARTIFACTS = "ADDITIONAL_ARTIFACTS"
1622
JMX_FILE = "JMX_FILE"

src/load/azext_load/data_plane/utils/utils.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from azure.mgmt.core.tools import is_valid_resource_id, parse_resource_id
2121
from knack.log import get_logger
2222

23-
from .models import IdentityType, AllowedFileTypes, AllowedTestTypes, AllowedTestPlanFileExtensions
23+
from .models import IdentityType, AllowedFileTypes, AllowedTestTypes, EngineIdentityType, AllowedTestPlanFileExtensions
2424

2525
logger = get_logger(__name__)
2626

@@ -306,6 +306,8 @@ def load_yaml(file_path):
306306
) from e
307307

308308

309+
# pylint: disable=line-too-long
310+
# Disabling this because dictionary key are too long
309311
def convert_yaml_to_test(cmd, data):
310312
new_body = {}
311313
if LoadTestConfigKeys.DISPLAY_NAME in data:
@@ -337,8 +339,11 @@ def convert_yaml_to_test(cmd, data):
337339
new_body["passFailCriteria"] = utils_yaml_config.yaml_parse_failure_criteria(data=data)
338340
if data.get(LoadTestConfigKeys.AUTOSTOP) is not None:
339341
new_body["autoStopCriteria"] = utils_yaml_config.yaml_parse_autostop_criteria(data=data)
342+
343+
utils_yaml_config.update_engine_reference_identity(new_body, data)
340344
logger.debug("Converted yaml to test body: %s", new_body)
341345
return new_body
346+
# pylint: enable=line-too-long
342347

343348

344349
# pylint: disable=too-many-branches
@@ -360,6 +365,8 @@ def create_or_update_test_with_config(
360365
disable_public_ip=None,
361366
autostop_criteria=None,
362367
regionwise_engines=None,
368+
engine_ref_id_type=None,
369+
engine_ref_ids=None,
363370
):
364371
logger.info(
365372
"Creating a request body for create or update test using config and parameters."
@@ -514,6 +521,20 @@ def create_or_update_test_with_config(
514521
"This can lead to incoming charges for an incorrectly configured test."
515522
)
516523

524+
# if argument is provided prefer that over yaml values
525+
if engine_ref_id_type:
526+
validators.validate_engine_ref_ids_and_type(engine_ref_id_type, engine_ref_ids)
527+
if engine_ref_id_type:
528+
new_body["engineBuiltinIdentityType"] = engine_ref_id_type
529+
if engine_ref_ids:
530+
new_body["engineBuiltinIdentityIds"] = engine_ref_ids
531+
elif yaml_test_body.get("engineBuiltinIdentityType"):
532+
new_body["engineBuiltinIdentityType"] = yaml_test_body.get("engineBuiltinIdentityType")
533+
new_body["engineBuiltinIdentityIds"] = yaml_test_body.get("engineBuiltinIdentityIds")
534+
else:
535+
new_body["engineBuiltinIdentityType"] = body.get("engineBuiltinIdentityType")
536+
new_body["engineBuiltinIdentityIds"] = body.get("engineBuiltinIdentityIds")
537+
517538
logger.debug("Request body for create or update test: %s", new_body)
518539
return new_body
519540

@@ -537,6 +558,8 @@ def create_or_update_test_without_config(
537558
autostop_criteria=None,
538559
regionwise_engines=None,
539560
baseline_test_run_id=None,
561+
engine_ref_id_type=None,
562+
engine_ref_ids=None,
540563
):
541564
logger.info(
542565
"Creating a request body for test using parameters and old test body (in case of update)."
@@ -640,6 +663,21 @@ def create_or_update_test_without_config(
640663
)
641664
new_body["baselineTestRunId"] = baseline_test_run_id if baseline_test_run_id else body.get("baselineTestRunId")
642665

666+
# pylint: disable=line-too-long
667+
# Disabling this because dictionary key are too long
668+
# raises error if engine_reference_identity_type and corresponding identities is not a valid combination
669+
validators.validate_engine_ref_ids_and_type(engine_ref_id_type, engine_ref_ids, body.get("engineBuiltinIdentityType"))
670+
if engine_ref_id_type:
671+
new_body["engineBuiltinIdentityType"] = engine_ref_id_type
672+
if engine_ref_ids:
673+
new_body["engineBuiltinIdentityIds"] = engine_ref_ids
674+
else:
675+
new_body["engineBuiltinIdentityType"] = body.get("engineBuiltinIdentityType")
676+
if engine_ref_ids and body.get("engineBuiltinIdentityType") != EngineIdentityType.UserAssigned:
677+
raise InvalidArgumentValueError("Engine reference identities can only be provided when engine reference identity type is user assigned")
678+
new_body["engineBuiltinIdentityIds"] = engine_ref_ids if engine_ref_ids else body.get("engineBuiltinIdentityIds")
679+
# pylint: enable=line-too-long
680+
643681
logger.debug("Request body for create or update test: %s", new_body)
644682
return new_body
645683

src/load/azext_load/data_plane/utils/utils_yaml_config.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from azext_load.data_plane.utils.constants import LoadTestConfigKeys
99
from azext_load.data_plane.utils import validators
10+
from azext_load.data_plane.utils.models import EngineIdentityType
11+
from azure.mgmt.core.tools import is_valid_resource_id
1012
from azure.cli.core.azclierror import (
1113
InvalidArgumentValueError,
1214
)
@@ -137,3 +139,43 @@ def yaml_parse_loadtest_configuration(cmd, data):
137139
if data.get(LoadTestConfigKeys.SPLIT_CSV) is not None:
138140
load_test_configuration["splitAllCSVs"] = _yaml_parse_splitcsv(data=data)
139141
return load_test_configuration
142+
143+
144+
# pylint: disable=line-too-long
145+
# Disabling this because dictionary key are too long
146+
def yaml_parse_engine_identities(data):
147+
engine_identities = []
148+
reference_type = None
149+
reference_identities = data.get(LoadTestConfigKeys.REFERENCE_IDENTITIES)
150+
for identity in reference_identities:
151+
if identity and identity.get(LoadTestConfigKeys.KIND) == LoadTestConfigKeys.ENGINE:
152+
curr_ref_type = identity.get(LoadTestConfigKeys.TYPE)
153+
curr_ref_value = identity.get(LoadTestConfigKeys.VALUE)
154+
if reference_type and curr_ref_type != reference_type:
155+
raise InvalidArgumentValueError(
156+
"Engine identity type should be either None, SystemAssigned, or UserAssigned. A combination of identity types are not supported."
157+
)
158+
if curr_ref_type != EngineIdentityType.UserAssigned:
159+
if curr_ref_value:
160+
raise InvalidArgumentValueError(
161+
"Reference identity value should be provided only for UserAssigned identity type."
162+
)
163+
else:
164+
if not is_valid_resource_id(curr_ref_value):
165+
raise InvalidArgumentValueError(
166+
"%s is not a valid resource id" % curr_ref_value
167+
)
168+
engine_identities.append(curr_ref_value)
169+
reference_type = curr_ref_type
170+
return reference_type, engine_identities
171+
172+
173+
def update_engine_reference_identity(new_body, data):
174+
if data.get(LoadTestConfigKeys.REFERENCE_IDENTITIES):
175+
for identity in data[LoadTestConfigKeys.REFERENCE_IDENTITIES]:
176+
if identity and identity.get(LoadTestConfigKeys.KIND) == LoadTestConfigKeys.ENGINE:
177+
new_body["engineBuiltinIdentityType"], new_body["engineBuiltinIdentityIds"] = yaml_parse_engine_identities(data=data)
178+
if new_body["engineBuiltinIdentityType"] in [EngineIdentityType.NoneValue, EngineIdentityType.SystemAssigned]:
179+
new_body.pop("engineBuiltinIdentityIds")
180+
break
181+
# pylint: enable=line-too-long

src/load/azext_load/data_plane/utils/validators.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
AllowedMetricNamespaces,
2222
AllowedTestTypes,
2323
AllowedTestPlanFileExtensions,
24+
EngineIdentityType,
2425
)
2526

2627
logger = get_logger(__name__)
@@ -522,3 +523,28 @@ def validate_regionwise_engines(cmd, namespace):
522523
)
523524
regionwise_engines.append({"region": key.strip().lower(), "engineInstances": value})
524525
namespace.regionwise_engines = regionwise_engines
526+
527+
528+
def validate_engine_ref_ids(namespace):
529+
"""Extracts multiple space-separated identities"""
530+
if isinstance(namespace.engine_ref_ids, list):
531+
for item in namespace.engine_ref_ids:
532+
if not is_valid_resource_id(item):
533+
raise InvalidArgumentValueError(f"Invalid engine-ref-ids value: {item}")
534+
535+
536+
def validate_engine_ref_ids_and_type(incoming_engine_ref_id_type, engine_ref_ids, exisiting_engine_ref_id_type=None):
537+
"""Validates combination of engine-ref-id-type and engine-ref-ids"""
538+
539+
# if engine_ref_id_type is None or SystemAssigned, then no value for engine_ref_ids is expected:
540+
engine_ref_id_type = incoming_engine_ref_id_type or exisiting_engine_ref_id_type
541+
if engine_ref_id_type != EngineIdentityType.UserAssigned and engine_ref_ids:
542+
raise InvalidArgumentValueError(
543+
"engine-ref-ids should not be provided when engine-ref-id-type is None or SystemAssigned"
544+
)
545+
546+
# If engine_ref_id_type is UserAssigned, then engine_ref_ids is expected.
547+
if incoming_engine_ref_id_type == EngineIdentityType.UserAssigned and engine_ref_ids is None:
548+
raise InvalidArgumentValueError(
549+
"Atleast one engine-ref-ids should be provided when engine-ref-id-type is UserAssigned"
550+
)

src/load/azext_load/tests/latest/constants.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@ class LoadTestConstants(LoadConstants):
157157
DESCRIPTION = r"Sample_test_description"
158158
DISPLAY_NAME = r"Sample_test_display_name"
159159

160+
# Constants for Engine MI tests
161+
ENGINE_REFERENCE_TYPE_USERASSIGNED = "UserAssigned"
162+
ENGINE_REFERENCE_TYPE_SYSTEMASSIGNED = "SystemAssigned"
163+
ENGINE_REFERENCE_TYPE_NONE = "None"
164+
ENGINE_REFERENCE_ID1 = r"/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/sample-rg/providers/microsoft.managedidentity/userassignedidentities/sample-mi"
165+
ENGINE_REFERENCE_ID2 = r"/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/sample-rg/providers/microsoft.managedidentity/userassignedidentities/sample-mi-2"
166+
INVALID_ENGINE_REFERENCE_ID = r"/subscriptions/invalid/resource/id"
167+
LOAD_TEST_CONFIG_FILE_WITH_SAMI_ENGINE = os.path.join(TEST_RESOURCES_DIR, r"config-engine-sami.yaml")
168+
LOAD_TEST_CONFIG_FILE_WITH_UAMI_ENGINE = os.path.join(TEST_RESOURCES_DIR, r"config-engine-uami.yaml")
169+
LOAD_TEST_CONFIG_FILE_WITH_INVALID_ENGINE_MI1 = os.path.join(TEST_RESOURCES_DIR, r"config-engine-invalid-mi1.yaml")
170+
LOAD_TEST_CONFIG_FILE_WITH_INVALID_ENGINE_MI2 = os.path.join(TEST_RESOURCES_DIR, r"config-engine-invalid-mi2.yaml")
171+
160172

161173
class LoadTestRunConstants(LoadConstants):
162174
# Metric constants

src/load/azext_load/tests/latest/recordings/test_load_app_component.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ interactions:
105105
{"aggregate": "p99.9", "clientMetric": "response_time_ms", "condition": ">",
106106
"value": "540"}, "1013b456-3a10-4e3c-a48f-1ba9ad44740c": {"aggregate": "avg",
107107
"clientMetric": "latency", "condition": ">", "value": "200", "requestName":
108-
"GetCustomerDetails"}}}, "autoStopCriteria": {"autoStopDisabled": true}}'
108+
"GetCustomerDetails"}}}, "autoStopCriteria": {"autoStopDisabled": true}, "engineBuiltinIdentityType":
109+
null, "engineBuiltinIdentityIds": null}'
109110
headers:
110111
Accept:
111112
- application/json
@@ -114,7 +115,7 @@ interactions:
114115
Connection:
115116
- keep-alive
116117
Content-Length:
117-
- '1307'
118+
- '1376'
118119
Content-Type:
119120
- application/merge-patch+json
120121
User-Agent:

0 commit comments

Comments
 (0)