From 612cf8a063fe6bd6af7963a380e6a8696e559258 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 21 May 2026 08:33:43 +0530 Subject: [PATCH 1/3] Adding QoS functionalities --- PyPowerStore/configuration.py | 112 ++++++++ PyPowerStore/protection.py | 96 +++++++ PyPowerStore/provisioning.py | 110 ++++++++ .../tests/unit_tests/data/common_data.py | 147 ++++++++++ .../unit_tests/entity/file_io_limit_rule.py | 66 +++++ .../tests/unit_tests/entity/io_limit_rule.py | 66 +++++ .../tests/unit_tests/entity/policy.py | 63 ++++- PyPowerStore/tests/unit_tests/myrequests.py | 6 + .../unit_tests/test_file_io_limit_rule.py | 255 ++++++++++++++++++ .../tests/unit_tests/test_io_limit_rule.py | 214 +++++++++++++++ .../tests/unit_tests/test_qos_policy.py | 249 +++++++++++++++++ PyPowerStore/utils/constants.py | 29 ++ 12 files changed, 1408 insertions(+), 5 deletions(-) create mode 100644 PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py create mode 100644 PyPowerStore/tests/unit_tests/entity/io_limit_rule.py create mode 100644 PyPowerStore/tests/unit_tests/test_file_io_limit_rule.py create mode 100644 PyPowerStore/tests/unit_tests/test_io_limit_rule.py create mode 100644 PyPowerStore/tests/unit_tests/test_qos_policy.py diff --git a/PyPowerStore/configuration.py b/PyPowerStore/configuration.py index 9bd2ae0..614c18e 100644 --- a/PyPowerStore/configuration.py +++ b/PyPowerStore/configuration.py @@ -2631,3 +2631,115 @@ def _prepare_create_cluster_payload(is_http_redirect_enabled, **kwargs): payload["security_config"] = security_config return payload + + # File IO Limit Rule methods (v4.1.0.0+) + + def get_file_io_limit_rules(self, filter_dict=None, all_pages=False): + """Get a list of file IO limit rules. + + :param filter_dict: (optional) Filter detail + :type filter_dict: dict + :param all_pages: (optional) Indicates whether to return all elements + :type all_pages: bool + :return: File IO limit rule list + :rtype: list of dict + """ + LOG.info( + "Getting file_io_limit_rules with filter: '%s' and all_pages: %s", + filter_dict, all_pages, + ) + querystring = helpers.prepare_querystring( + constants.SELECT_ID_AND_NAME, filter_dict, + ) + return self.config_client.request( + constants.GET, + constants.FILE_IO_LIMIT_RULE_LIST_URL.format(self.server_ip), + payload=None, + querystring=querystring, + all_pages=all_pages, + ) + + def get_file_io_limit_rule_details(self, rule_id): + """Get details of a file IO limit rule. + + :param rule_id: The file IO limit rule ID + :type rule_id: str + :return: File IO limit rule details + :rtype: dict + """ + LOG.info("Getting file_io_limit_rule details by ID: '%s'", rule_id) + return self.config_client.request( + constants.GET, + constants.FILE_IO_LIMIT_RULE_OBJECT_URL.format(self.server_ip, rule_id), + payload=None, + querystring=constants.FILE_IO_LIMIT_RULE_DETAILS_QUERY, + ) + + def get_file_io_limit_rule_by_name(self, rule_name): + """Get file IO limit rule details by name. + + :param rule_name: The file IO limit rule name + :type rule_name: str + :return: File IO limit rule details + :rtype: list of dict + """ + LOG.info("Getting file_io_limit_rule details by name: '%s'", rule_name) + return self.config_client.request( + constants.GET, + constants.FILE_IO_LIMIT_RULE_LIST_URL.format(self.server_ip), + payload=None, + querystring=helpers.prepare_querystring( + constants.FILE_IO_LIMIT_RULE_DETAILS_QUERY, + name=constants.EQUALS + rule_name, + ), + ) + + def create_file_io_limit_rule(self, payload): + """Create a new file IO limit rule. + + :param payload: Request payload (must contain 'name' and 'max_bw') + :type payload: dict + :return: ID of the created file IO limit rule + :rtype: dict + """ + LOG.info("Creating file_io_limit_rule with payload: '%s'", payload) + return self.config_client.request( + constants.POST, + constants.FILE_IO_LIMIT_RULE_LIST_URL.format(self.server_ip), + payload=payload, + ) + + def modify_file_io_limit_rule(self, rule_id, payload): + """Modify a file IO limit rule. + + :param rule_id: The file IO limit rule ID + :type rule_id: str + :param payload: Modification payload + :type payload: dict + :return: None + :rtype: None + """ + LOG.info( + "Modifying file_io_limit_rule '%s' with payload: '%s'", rule_id, payload, + ) + return self.config_client.request( + constants.PATCH, + constants.FILE_IO_LIMIT_RULE_OBJECT_URL.format(self.server_ip, rule_id), + payload=payload, + ) + + def delete_file_io_limit_rule(self, rule_id): + """Delete a file IO limit rule. + + :param rule_id: The file IO limit rule ID + :type rule_id: str + :return: None + :rtype: None + """ + LOG.info("Deleting file_io_limit_rule: '%s'", rule_id) + return self.config_client.request( + constants.DELETE, + constants.FILE_IO_LIMIT_RULE_OBJECT_URL.format(self.server_ip, rule_id), + ) + + # File IO Limit Rule methods end diff --git a/PyPowerStore/protection.py b/PyPowerStore/protection.py index a2a7acc..ddd12e1 100644 --- a/PyPowerStore/protection.py +++ b/PyPowerStore/protection.py @@ -847,6 +847,102 @@ def delete_protection_policy(self, policy_id): constants.PROTECTION_POLICY_OBJECT_URL.format(self.server_ip, policy_id), ) + # QoS / File_Performance Policy Methods + + def get_policy_details(self, policy_id): + """Get details of a QoS or File_Performance policy by ID. + + :param policy_id: Policy unique identifier. + :type policy_id: str + :return: Policy details. + :rtype: dict + """ + LOG.info("Getting QoS policy details by ID: '%s'", policy_id) + # Use singular field names: io_limit_rule, file_io_limit_rule + # The API reference incorrectly shows plural forms + querystring = { + "select": "id,name,description,type,type_l10n,io_limit_rule(id,name),file_io_limit_rule(id,name)", + } + return self.rest_client.request( + constants.GET, + constants.PROTECTION_POLICY_OBJECT_URL.format(self.server_ip, policy_id), + querystring=querystring, + ) + + def get_policy_by_name(self, name, policy_type=None): + """Get a QoS or File_Performance policy by name. + + :param name: Policy name. + :type name: str + :param policy_type: Optional policy type filter (QoS or File_Performance). + :type policy_type: str + :return: List of matching policies. + :rtype: list[dict] + """ + LOG.info("Getting QoS policy details by name: '%s'", name) + # Use singular field names: io_limit_rule, file_io_limit_rule + querystring = { + "select": "id,name,description,type,type_l10n,io_limit_rule(id,name),file_io_limit_rule(id,name)", + "name": constants.EQUALS + name, + } + if policy_type: + querystring["type"] = constants.EQUALS + policy_type + return self.rest_client.request( + constants.GET, + constants.PROTECTION_POLICY_LIST_URL.format(self.server_ip), + querystring=querystring, + ) + + def create_policy(self, payload): + """Create a new QoS or File_Performance policy. + + :param payload: Request payload containing 'name', and either + 'io_limit_rule_id' (QoS) or 'file_io_limit_rule_id' + (File_Performance). + :type payload: dict + :return: Response dict with policy ID. + :rtype: dict + """ + LOG.info("Creating QoS policy with payload: '%s'", payload) + return self.rest_client.request( + constants.POST, + constants.PROTECTION_POLICY_LIST_URL.format(self.server_ip), + payload, + ) + + def modify_policy(self, policy_id, payload): + """Modify a QoS or File_Performance policy. + + :param policy_id: Policy unique identifier. + :type policy_id: str + :param payload: Modification payload. + :type payload: dict + :return: None + :rtype: None + """ + LOG.info("Modifying QoS policy '%s' with payload: '%s'", policy_id, payload) + return self.rest_client.request( + constants.PATCH, + constants.PROTECTION_POLICY_OBJECT_URL.format(self.server_ip, policy_id), + payload, + ) + + def delete_policy(self, policy_id): + """Delete a QoS or File_Performance policy. + + :param policy_id: Policy unique identifier. + :type policy_id: str + :return: None if success else raise exception. + :rtype: None + """ + LOG.info("Deleting QoS policy: '%s'", policy_id) + return self.rest_client.request( + constants.DELETE, + constants.PROTECTION_POLICY_OBJECT_URL.format(self.server_ip, policy_id), + ) + + # QoS / File_Performance Policy Methods End + # FS Snapshot Methods def get_filesystem_snapshot_details_by_name( diff --git a/PyPowerStore/provisioning.py b/PyPowerStore/provisioning.py index 9aae77a..aaf32d8 100644 --- a/PyPowerStore/provisioning.py +++ b/PyPowerStore/provisioning.py @@ -2709,3 +2709,113 @@ def get_file_ldaps(self, filter_dict=None, all_pages=False): ) # LDAP method end + + # IO Limit Rule methods (v4.0.0.0+) + + def get_io_limit_rules(self, filter_dict=None, all_pages=False): + """Get a list of IO limit rules. + + :param filter_dict: (optional) Filter detail + :type filter_dict: dict + :param all_pages: (optional) Indicates whether to return all elements + :type all_pages: bool + :return: IO limit rule list + :rtype: list of dict + """ + LOG.info( + "Getting io_limit_rules with filter: '%s' and all_pages: %s", + filter_dict, all_pages, + ) + querystring = helpers.prepare_querystring( + constants.SELECT_ID_AND_NAME, filter_dict, + ) + return self.client.request( + constants.GET, + constants.IO_LIMIT_RULE_LIST_URL.format(self.server_ip), + payload=None, + querystring=querystring, + all_pages=all_pages, + ) + + def get_io_limit_rule_details(self, rule_id): + """Get details of an IO limit rule. + + :param rule_id: The IO limit rule ID + :type rule_id: str + :return: IO limit rule details + :rtype: dict + """ + LOG.info("Getting io_limit_rule details by ID: '%s'", rule_id) + return self.client.request( + constants.GET, + constants.IO_LIMIT_RULE_OBJECT_URL.format(self.server_ip, rule_id), + payload=None, + querystring=constants.IO_LIMIT_RULE_DETAILS_QUERY, + ) + + def get_io_limit_rule_by_name(self, rule_name): + """Get IO limit rule details by name. + + :param rule_name: The IO limit rule name + :type rule_name: str + :return: IO limit rule details + :rtype: list of dict + """ + LOG.info("Getting io_limit_rule details by name: '%s'", rule_name) + return self.client.request( + constants.GET, + constants.IO_LIMIT_RULE_LIST_URL.format(self.server_ip), + payload=None, + querystring=helpers.prepare_querystring( + constants.IO_LIMIT_RULE_DETAILS_QUERY, + name=constants.EQUALS + rule_name, + ), + ) + + def create_io_limit_rule(self, payload): + """Create a new IO limit rule. + + :param payload: Request payload + :type payload: dict + :return: ID of the created IO limit rule + :rtype: dict + """ + LOG.info("Creating io_limit_rule with payload: '%s'", payload) + return self.client.request( + constants.POST, + constants.IO_LIMIT_RULE_LIST_URL.format(self.server_ip), + payload=payload, + ) + + def modify_io_limit_rule(self, rule_id, payload): + """Modify an IO limit rule. + + :param rule_id: The IO limit rule ID + :type rule_id: str + :param payload: Modification payload + :type payload: dict + :return: None + :rtype: None + """ + LOG.info("Modifying io_limit_rule '%s' with payload: '%s'", rule_id, payload) + return self.client.request( + constants.PATCH, + constants.IO_LIMIT_RULE_OBJECT_URL.format(self.server_ip, rule_id), + payload=payload, + ) + + def delete_io_limit_rule(self, rule_id): + """Delete an IO limit rule. + + :param rule_id: The IO limit rule ID + :type rule_id: str + :return: None + :rtype: None + """ + LOG.info("Deleting io_limit_rule: '%s'", rule_id) + return self.client.request( + constants.DELETE, + constants.IO_LIMIT_RULE_OBJECT_URL.format(self.server_ip, rule_id), + ) + + # IO Limit Rule methods end diff --git a/PyPowerStore/tests/unit_tests/data/common_data.py b/PyPowerStore/tests/unit_tests/data/common_data.py index 14374b7..863b319 100644 --- a/PyPowerStore/tests/unit_tests/data/common_data.py +++ b/PyPowerStore/tests/unit_tests/data/common_data.py @@ -344,6 +344,153 @@ class CommonData: # ProtectionPolicy End + # IO Limit Rule + io_limit_rule_id1 = "a1b2c3d4-1111-2222-3333-aabbccddeeff" + io_limit_rule_name1 = "my_io_rule1" + io_limit_rule_id2 = "b2c3d4e5-2222-3333-4444-bbccddeeff00" + io_limit_rule_name2 = "my_io_rule2" + invalid_io_limit_rule_id = "00000000-0000-0000-0000-000000000bad" + + io_limit_rule_list = [ + {"id": io_limit_rule_id1, "name": io_limit_rule_name1}, + {"id": io_limit_rule_id2, "name": io_limit_rule_name2}, + ] + + io_limit_rule1 = { + "id": io_limit_rule_id1, + "name": io_limit_rule_name1, + "max_bw": 102400, + "max_iops": 5000, + "burst_percentage": 20, + "type": "Absolute", + "type_l10n": "Absolute", + "policies": [], + } + + io_limit_rule1_modified = { + "id": io_limit_rule_id1, + "name": io_limit_rule_name1, + "max_bw": 204800, + "max_iops": 10000, + "burst_percentage": 20, + "type": "Absolute", + "type_l10n": "Absolute", + "policies": [], + } + + io_limit_rule_error = { + 404: { + "messages": [ + { + "arguments": [invalid_io_limit_rule_id], + "code": "0xE0A090010001", + "message_l10n": f"Unable to find the IO limit rule with ID {invalid_io_limit_rule_id}", + "severity": "Error", + }, + ], + }, + } + + # IO Limit Rule End + + # File IO Limit Rule + file_io_limit_rule_id1 = "c3d4e5f6-3333-4444-5555-ccddeeff0011" + file_io_limit_rule_name1 = "my_file_rule1" + file_io_limit_rule_id2 = "d4e5f6a7-4444-5555-6666-ddeeff001122" + file_io_limit_rule_name2 = "my_file_rule2" + invalid_file_io_limit_rule_id = "00000000-0000-0000-0000-000000000bad" + + file_io_limit_rule_list = [ + {"id": file_io_limit_rule_id1, "name": file_io_limit_rule_name1}, + {"id": file_io_limit_rule_id2, "name": file_io_limit_rule_name2}, + ] + + file_io_limit_rule1 = { + "id": file_io_limit_rule_id1, + "name": file_io_limit_rule_name1, + "max_bw": 500, + "policies": [], + } + + file_io_limit_rule1_modified = { + "id": file_io_limit_rule_id1, + "name": file_io_limit_rule_name1, + "max_bw": 1000, + "policies": [], + } + + file_io_limit_rule_error = { + 404: { + "messages": [ + { + "arguments": [invalid_file_io_limit_rule_id], + "code": "0xE0A090010001", + "message_l10n": f"Unable to find the file IO limit rule with ID {invalid_file_io_limit_rule_id}", + "severity": "Error", + }, + ], + }, + } + + # File IO Limit Rule End + + # QoS Policy + qos_policy_id1 = "e5f6a7b8-5555-6666-7777-eeff00112233" + qos_policy_name1 = "my_qos_policy1" + qos_policy_id2 = "f6a7b8c9-6666-7777-8888-ff0011223344" + qos_policy_name2 = "my_file_perf_policy1" + invalid_qos_policy_id = "00000000-0000-0000-0000-000000000bad" + + qos_policy_list = [ + {"id": qos_policy_id1, "name": qos_policy_name1}, + {"id": qos_policy_id2, "name": qos_policy_name2}, + ] + + qos_policy1 = { + "id": qos_policy_id1, + "name": qos_policy_name1, + "description": "Gold QoS policy", + "type": "QoS", + "type_l10n": "QoS", + "io_limit_rule": {"id": io_limit_rule_id1, "name": io_limit_rule_name1}, + "file_io_limit_rule": None, + } + + qos_policy1_modified = { + "id": qos_policy_id1, + "name": qos_policy_name1, + "description": "Updated Gold QoS policy", + "type": "QoS", + "type_l10n": "QoS", + "io_limit_rule": {"id": io_limit_rule_id2, "name": io_limit_rule_name2}, + "file_io_limit_rule": None, + } + + file_perf_policy1 = { + "id": qos_policy_id2, + "name": qos_policy_name2, + "description": "", + "type": "File_Performance", + "type_l10n": "File_Performance", + "io_limit_rule": None, + "file_io_limit_rule": {"id": file_io_limit_rule_id1, "name": file_io_limit_rule_name1}, + } + + qos_policy_error = { + 404: { + "messages": [ + { + "arguments": [invalid_qos_policy_id], + "code": "0xE0A090010001", + "message_l10n": f"Unable to find the policy with ID {invalid_qos_policy_id}", + "severity": "Error", + }, + ], + }, + } + + # QoS Policy End + # SnapshotRule snap_rule_id1 = "f24c1295-f73f-48f3-8e82-3e45c5444fcc" snap_rule_name1 = "my_sn_rule1" diff --git a/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py b/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py new file mode 100644 index 0000000..10d0dc5 --- /dev/null +++ b/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py @@ -0,0 +1,66 @@ +"""Mock File IO Limit Rule API for File IO Limit Rule Unit Tests""" + +# pylint: disable=too-many-return-statements,duplicate-code + +from PyPowerStore.tests.unit_tests.data.common_data import CommonData +from PyPowerStore.tests.unit_tests.entity.base_abstract import Entity + + +class FileIoLimitRuleResponse(Entity): + """ + This class is used to handle File IO Limit Rule related responses. + + It provides methods to get file_io_limit_rules, get file_io_limit_rule by name, + get file_io_limit_rule details, create file_io_limit_rule, modify file_io_limit_rule, + and delete file_io_limit_rule. + """ + + def __init__(self, method, url, **kwargs): + self.method = method + self.url = url + self.kwargs = kwargs + self.data = CommonData() + self.status_code = 200 + + def get_api_name(self): + if self.method == "GET": + if self.url.endswith("/file_io_limit_rule"): + if self.kwargs.get("params", {}).get("name"): + return self.get_file_io_limit_rule_by_name + return self.get_file_io_limit_rules + return self.get_file_io_limit_rule_details + if self.method == "POST": + return self.create_file_io_limit_rule + if self.method == "PATCH": + return self.modify_file_io_limit_rule + if self.method == "DELETE": + return self.delete_file_io_limit_rule + return None + + def execute_api(self, api_name): + status_code, response = api_name() + return status_code, response + + def get_file_io_limit_rules(self): + return self.status_code, self.data.file_io_limit_rule_list + + def get_file_io_limit_rule_by_name(self): + return self.status_code, [self.data.file_io_limit_rule1] + + def get_file_io_limit_rule_details(self): + if self.data.invalid_file_io_limit_rule_id in self.url: + return 404, self.data.file_io_limit_rule_error[404] + return self.status_code, self.data.file_io_limit_rule1 + + def create_file_io_limit_rule(self): + return 201, {"id": self.data.file_io_limit_rule_id1} + + def modify_file_io_limit_rule(self): + if self.data.invalid_file_io_limit_rule_id in self.url: + return 404, self.data.file_io_limit_rule_error[404] + return 204, None + + def delete_file_io_limit_rule(self): + if self.data.invalid_file_io_limit_rule_id in self.url: + return 404, self.data.file_io_limit_rule_error[404] + return 204, None diff --git a/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py b/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py new file mode 100644 index 0000000..6b8ec8f --- /dev/null +++ b/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py @@ -0,0 +1,66 @@ +"""Mock IO Limit Rule API for IO Limit Rule Unit Tests""" + +# pylint: disable=too-many-return-statements,duplicate-code + +from PyPowerStore.tests.unit_tests.data.common_data import CommonData +from PyPowerStore.tests.unit_tests.entity.base_abstract import Entity + + +class IoLimitRuleResponse(Entity): + """ + This class is used to handle IO Limit Rule related responses. + + It provides methods to get io_limit_rules, get io_limit_rule by name, + get io_limit_rule details, create io_limit_rule, modify io_limit_rule, + and delete io_limit_rule. + """ + + def __init__(self, method, url, **kwargs): + self.method = method + self.url = url + self.kwargs = kwargs + self.data = CommonData() + self.status_code = 200 + + def get_api_name(self): + if self.method == "GET": + if self.url.endswith("/io_limit_rule"): + if self.kwargs.get("params", {}).get("name"): + return self.get_io_limit_rule_by_name + return self.get_io_limit_rules + return self.get_io_limit_rule_details + if self.method == "POST": + return self.create_io_limit_rule + if self.method == "PATCH": + return self.modify_io_limit_rule + if self.method == "DELETE": + return self.delete_io_limit_rule + return None + + def execute_api(self, api_name): + status_code, response = api_name() + return status_code, response + + def get_io_limit_rules(self): + return self.status_code, self.data.io_limit_rule_list + + def get_io_limit_rule_by_name(self): + return self.status_code, [self.data.io_limit_rule1] + + def get_io_limit_rule_details(self): + if self.data.invalid_io_limit_rule_id in self.url: + return 404, self.data.io_limit_rule_error[404] + return self.status_code, self.data.io_limit_rule1 + + def create_io_limit_rule(self): + return 201, {"id": self.data.io_limit_rule_id1} + + def modify_io_limit_rule(self): + if self.data.invalid_io_limit_rule_id in self.url: + return 404, self.data.io_limit_rule_error[404] + return 204, None + + def delete_io_limit_rule(self): + if self.data.invalid_io_limit_rule_id in self.url: + return 404, self.data.io_limit_rule_error[404] + return 204, None diff --git a/PyPowerStore/tests/unit_tests/entity/policy.py b/PyPowerStore/tests/unit_tests/entity/policy.py index d49a2f1..8239eb0 100644 --- a/PyPowerStore/tests/unit_tests/entity/policy.py +++ b/PyPowerStore/tests/unit_tests/entity/policy.py @@ -9,16 +9,17 @@ class PolicyResponse(Entity): """ This class is used to handle Policy related responses. - + It provides methods to get policies, get protection policy by name, get protection policy details, create protection policy, modify protection - policy and delete protection policy. + policy and delete protection policy. Also handles QoS / File_Performance + policy operations which share the same /policy endpoint. """ def __init__(self, method, url, **kwargs): """ Initializes the PolicyResponse object. - + Args: method (str): The HTTP method. url (str): The URL of the request. @@ -33,21 +34,48 @@ def __init__(self, method, url, **kwargs): def get_api_name(self): """ Returns the name of the API based on the HTTP method and URL. - + Returns: function: The API function. """ if self.method == "GET": if self.url.endswith("/policy"): - if self.kwargs.get("params", {}).get("name"): + params = self.kwargs.get("params", {}) + if params.get("name"): + # Distinguish QoS vs Protection policy by checking select fields + select = params.get("select", "") + if "io_limit_rule" in select: + return self.get_qos_policy_by_name return self.get_protection_policy_by_name return self.get_policies + # Detail by ID — distinguish by checking if it's a QoS policy ID + if ( + self.data.qos_policy_id1 in self.url + or self.data.qos_policy_id2 in self.url + or self.data.invalid_qos_policy_id in self.url + ): + return self.get_qos_policy_details return self.get_protection_policy_details if self.method == "POST": + data = self.kwargs.get("data", {}) + if "io_limit_rule_id" in data or "file_io_limit_rule_id" in data: + return self.create_qos_policy return self.create_protection_policy if self.method == "PATCH": + if ( + self.data.qos_policy_id1 in self.url + or self.data.qos_policy_id2 in self.url + or self.data.invalid_qos_policy_id in self.url + ): + return self.modify_qos_policy return self.modify_protection_policy if self.method == "DELETE": + if ( + self.data.qos_policy_id1 in self.url + or self.data.qos_policy_id2 in self.url + or self.data.invalid_qos_policy_id in self.url + ): + return self.delete_qos_policy return self.delete_protection_policy return None @@ -129,3 +157,28 @@ def delete_protection_policy(self): tuple: A tuple containing the status code and the response. """ return 204, None + + def get_qos_policy_by_name(self): + return self.status_code, [self.data.qos_policy1] + + def get_qos_policy_details(self): + if self.data.qos_policy_id2 in self.url: + return self.status_code, self.data.file_perf_policy1 + return self.status_code, self.data.qos_policy1 + + def create_qos_policy(self): + return 201, {"id": self.data.qos_policy_id1} + + def modify_qos_policy(self): + if self.data.invalid_qos_policy_id in self.url: + return 404, self.data.qos_policy_error[404] + if self.data.qos_policy_id1 not in self.url and self.data.qos_policy_id2 not in self.url: + return 404, self.data.qos_policy_error[404] + return 204, self.data.qos_policy1_modified + + def delete_qos_policy(self): + if self.data.invalid_qos_policy_id in self.url: + return 404, self.data.qos_policy_error[404] + if self.data.qos_policy_id1 not in self.url and self.data.qos_policy_id2 not in self.url: + return 404, self.data.qos_policy_error[404] + return 204, None diff --git a/PyPowerStore/tests/unit_tests/myrequests.py b/PyPowerStore/tests/unit_tests/myrequests.py index 278731b..40db568 100644 --- a/PyPowerStore/tests/unit_tests/myrequests.py +++ b/PyPowerStore/tests/unit_tests/myrequests.py @@ -30,6 +30,10 @@ from PyPowerStore.tests.unit_tests.entity.nfs_export import NFSExportResponse from PyPowerStore.tests.unit_tests.entity.nfs_server import NFSServerResponse from PyPowerStore.tests.unit_tests.entity.ntp import NtpResponse +from PyPowerStore.tests.unit_tests.entity.file_io_limit_rule import ( + FileIoLimitRuleResponse, +) +from PyPowerStore.tests.unit_tests.entity.io_limit_rule import IoLimitRuleResponse from PyPowerStore.tests.unit_tests.entity.policy import PolicyResponse from PyPowerStore.tests.unit_tests.entity.remote_support import RemoteSupportResponse from PyPowerStore.tests.unit_tests.entity.remote_support_contact import ( @@ -120,6 +124,8 @@ "file_dns": FileDNSResponse, "file_nis": FileNISResponse, "snmp_server": SNMPServerResponse, + "io_limit_rule": IoLimitRuleResponse, + "file_io_limit_rule": FileIoLimitRuleResponse, } diff --git a/PyPowerStore/tests/unit_tests/test_file_io_limit_rule.py b/PyPowerStore/tests/unit_tests/test_file_io_limit_rule.py new file mode 100644 index 0000000..a50a240 --- /dev/null +++ b/PyPowerStore/tests/unit_tests/test_file_io_limit_rule.py @@ -0,0 +1,255 @@ +"""Unit tests for File IO Limit Rule""" + +from unittest import mock + +from PyPowerStore.tests.unit_tests.base_test import TestBase +from PyPowerStore.utils import constants +from PyPowerStore.utils.exception import PowerStoreException + + +class TestFileIoLimitRule(TestBase): + """ + Unit tests for File IO Limit Rule SDK methods on configuration. + """ + + def test_get_file_io_limit_rules(self): + """ + Test get file_io_limit_rules + + Validates that the returned list equals self.data.file_io_limit_rule_list + """ + result = self.configuration.get_file_io_limit_rules() + self.assertListEqual(result, self.data.file_io_limit_rule_list) + + def test_get_file_io_limit_rules_with_filter(self): + """ + Test get file_io_limit_rules with filter and all_pages + + Validates that mock_request is called once + """ + filter_dict = {"name": constants.EQUALS + self.data.file_io_limit_rule_name1} + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = [self.data.file_io_limit_rule1] + self.configuration.get_file_io_limit_rules( + filter_dict=filter_dict, all_pages=True, + ) + mock_request.assert_called_once() + + def test_get_file_io_limit_rule_details(self): + """ + Test get file_io_limit_rule details by ID + + Validates that rule details equals self.data.file_io_limit_rule1 + """ + result = self.configuration.get_file_io_limit_rule_details( + self.data.file_io_limit_rule_id1, + ) + self.assertEqual(result, self.data.file_io_limit_rule1) + + def test_get_file_io_limit_rule_details_invalid_id(self): + """ + Test get file_io_limit_rule details with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.configuration.get_file_io_limit_rule_details, + self.data.invalid_file_io_limit_rule_id, + ) + + def test_get_file_io_limit_rule_by_name(self): + """ + Test get file_io_limit_rule by name + + Validates that rule details list equals [self.data.file_io_limit_rule1] + """ + result = self.configuration.get_file_io_limit_rule_by_name( + self.data.file_io_limit_rule_name1, + ) + self.assertEqual(result, [self.data.file_io_limit_rule1]) + + def test_get_file_io_limit_rule_by_name_uses_correct_url(self): + """ + Test get file_io_limit_rule by name uses FILE_IO_LIMIT_RULE_LIST_URL + + Validates that mock_request is called with the list URL + """ + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = [self.data.file_io_limit_rule1] + self.configuration.get_file_io_limit_rule_by_name( + self.data.file_io_limit_rule_name1, + ) + args, _ = mock_request.call_args + self.assertIn( + constants.FILE_IO_LIMIT_RULE_LIST_URL.format(self.configuration.server_ip), + args, + ) + + def test_create_file_io_limit_rule(self): + """ + Test create file_io_limit_rule + + Validates that the returned dict contains the expected rule ID + """ + payload = { + "name": self.data.file_io_limit_rule_name1, + "max_bw": 500, + } + result = self.configuration.create_file_io_limit_rule(payload) + self.assertEqual(result.get("id"), self.data.file_io_limit_rule_id1) + + def test_create_file_io_limit_rule_uses_post(self): + """ + Test create file_io_limit_rule uses POST method + + Validates that mock_request is called with POST + """ + payload = {"name": self.data.file_io_limit_rule_name1, "max_bw": 100} + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = {"id": self.data.file_io_limit_rule_id1} + self.configuration.create_file_io_limit_rule(payload) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.POST) + + def test_create_file_io_limit_rule_uses_correct_url(self): + """ + Test create file_io_limit_rule uses FILE_IO_LIMIT_RULE_LIST_URL + + Validates that mock_request is called with the list URL + """ + payload = {"name": self.data.file_io_limit_rule_name1, "max_bw": 100} + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = {"id": self.data.file_io_limit_rule_id1} + self.configuration.create_file_io_limit_rule(payload) + args, _ = mock_request.call_args + self.assertEqual( + args[1], + constants.FILE_IO_LIMIT_RULE_LIST_URL.format(self.configuration.server_ip), + ) + + def test_modify_file_io_limit_rule(self): + """ + Test modify file_io_limit_rule + + Validates that modify returns None (204 No Content) + """ + payload = {"max_bw": 1000} + result = self.configuration.modify_file_io_limit_rule( + self.data.file_io_limit_rule_id1, payload, + ) + self.assertIsNone(result) + + def test_modify_file_io_limit_rule_invalid_id(self): + """ + Test modify file_io_limit_rule with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.configuration.modify_file_io_limit_rule, + self.data.invalid_file_io_limit_rule_id, + {"max_bw": 1000}, + ) + + def test_modify_file_io_limit_rule_uses_patch(self): + """ + Test modify file_io_limit_rule uses PATCH method + + Validates that mock_request is called with PATCH and the rule object URL + """ + payload = {"max_bw": 1000} + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = None + self.configuration.modify_file_io_limit_rule( + self.data.file_io_limit_rule_id1, payload, + ) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.PATCH) + self.assertIn(self.data.file_io_limit_rule_id1, args[1]) + + def test_modify_file_io_limit_rule_rename(self): + """ + Test modify file_io_limit_rule with rename payload + + Validates that mock_request is called with name in payload + """ + payload = {"name": "renamed_rule"} + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = None + self.configuration.modify_file_io_limit_rule( + self.data.file_io_limit_rule_id1, payload, + ) + _, kwargs = mock_request.call_args + self.assertEqual(kwargs.get("payload"), payload) + + def test_delete_file_io_limit_rule(self): + """ + Test delete file_io_limit_rule + + Validates that delete returns None (204 No Content) + """ + result = self.configuration.delete_file_io_limit_rule( + self.data.file_io_limit_rule_id1, + ) + self.assertIsNone(result) + + def test_delete_file_io_limit_rule_invalid_id(self): + """ + Test delete file_io_limit_rule with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.configuration.delete_file_io_limit_rule, + self.data.invalid_file_io_limit_rule_id, + ) + + def test_delete_file_io_limit_rule_uses_delete(self): + """ + Test delete file_io_limit_rule uses DELETE method + + Validates that mock_request is called with DELETE and the rule object URL + """ + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = None + self.configuration.delete_file_io_limit_rule(self.data.file_io_limit_rule_id1) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.DELETE) + self.assertIn(self.data.file_io_limit_rule_id1, args[1]) + + def test_get_file_io_limit_rule_details_uses_object_url(self): + """ + Test get file_io_limit_rule details uses FILE_IO_LIMIT_RULE_OBJECT_URL + + Validates that the URL contains the rule ID + """ + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = self.data.file_io_limit_rule1 + self.configuration.get_file_io_limit_rule_details( + self.data.file_io_limit_rule_id1, + ) + args, _ = mock_request.call_args + expected_url = constants.FILE_IO_LIMIT_RULE_OBJECT_URL.format( + self.configuration.server_ip, self.data.file_io_limit_rule_id1, + ) + self.assertEqual(args[1], expected_url) + + def test_get_file_io_limit_rule_details_query_select(self): + """ + Test get file_io_limit_rule details uses FILE_IO_LIMIT_RULE_DETAILS_QUERY + + Validates that the querystring includes the correct select fields + """ + with mock.patch.object(self.configuration.config_client, "request") as mock_request: + mock_request.return_value = self.data.file_io_limit_rule1 + self.configuration.get_file_io_limit_rule_details( + self.data.file_io_limit_rule_id1, + ) + _, kwargs = mock_request.call_args + self.assertEqual( + kwargs.get("querystring"), + constants.FILE_IO_LIMIT_RULE_DETAILS_QUERY, + ) diff --git a/PyPowerStore/tests/unit_tests/test_io_limit_rule.py b/PyPowerStore/tests/unit_tests/test_io_limit_rule.py new file mode 100644 index 0000000..6794c5b --- /dev/null +++ b/PyPowerStore/tests/unit_tests/test_io_limit_rule.py @@ -0,0 +1,214 @@ +"""Unit tests for IO Limit Rule""" + +from unittest import mock + +from PyPowerStore.tests.unit_tests.base_test import TestBase +from PyPowerStore.utils import constants +from PyPowerStore.utils.exception import PowerStoreException + + +class TestIoLimitRule(TestBase): + """ + Unit tests for IO Limit Rule SDK methods on provisioning. + """ + + def test_get_io_limit_rules(self): + """ + Test get io_limit_rules + + Validates that the returned list equals self.data.io_limit_rule_list + """ + result = self.provisioning.get_io_limit_rules() + self.assertListEqual(result, self.data.io_limit_rule_list) + + def test_get_io_limit_rules_with_filter(self): + """ + Test get io_limit_rules with filter and all_pages + + Validates that mock_request is called with the correct arguments + """ + filter_dict = {"name": constants.EQUALS + self.data.io_limit_rule_name1} + with mock.patch.object(self.provisioning.client, "request") as mock_request: + self.provisioning.get_io_limit_rules( + filter_dict=filter_dict, all_pages=True, + ) + mock_request.assert_called_once() + + def test_get_io_limit_rule_details(self): + """ + Test get io_limit_rule details by ID + + Validates that rule details equals self.data.io_limit_rule1 + """ + result = self.provisioning.get_io_limit_rule_details( + self.data.io_limit_rule_id1, + ) + self.assertEqual(result, self.data.io_limit_rule1) + + def test_get_io_limit_rule_details_invalid_id(self): + """ + Test get io_limit_rule details with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.provisioning.get_io_limit_rule_details, + self.data.invalid_io_limit_rule_id, + ) + + def test_get_io_limit_rule_by_name(self): + """ + Test get io_limit_rule by name + + Validates that rule details list equals [self.data.io_limit_rule1] + """ + result = self.provisioning.get_io_limit_rule_by_name( + self.data.io_limit_rule_name1, + ) + self.assertEqual(result, [self.data.io_limit_rule1]) + + def test_get_io_limit_rule_by_name_uses_correct_url(self): + """ + Test get io_limit_rule by name uses IO_LIMIT_RULE_LIST_URL + + Validates that mock_request is called with the list URL and name filter + """ + with mock.patch.object(self.provisioning.client, "request") as mock_request: + mock_request.return_value = [self.data.io_limit_rule1] + self.provisioning.get_io_limit_rule_by_name(self.data.io_limit_rule_name1) + args, kwargs = mock_request.call_args + self.assertIn( + constants.IO_LIMIT_RULE_LIST_URL.format(self.provisioning.server_ip), + args, + ) + + def test_create_io_limit_rule(self): + """ + Test create io_limit_rule + + Validates that the returned dict contains the expected rule ID + """ + payload = { + "name": self.data.io_limit_rule_name1, + "type": "Absolute", + "max_iops": 5000, + "max_bw": 102400, + } + result = self.provisioning.create_io_limit_rule(payload) + self.assertEqual(result.get("id"), self.data.io_limit_rule_id1) + + def test_create_io_limit_rule_uses_post(self): + """ + Test create io_limit_rule uses POST method + + Validates that mock_request is called with POST + """ + payload = {"name": self.data.io_limit_rule_name1, "type": "Absolute", "max_iops": 1} + with mock.patch.object(self.provisioning.client, "request") as mock_request: + mock_request.return_value = {"id": self.data.io_limit_rule_id1} + self.provisioning.create_io_limit_rule(payload) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.POST) + + def test_modify_io_limit_rule(self): + """ + Test modify io_limit_rule + + Validates that modify returns None (204 No Content) + """ + payload = {"max_iops": 10000} + result = self.provisioning.modify_io_limit_rule( + self.data.io_limit_rule_id1, payload, + ) + self.assertIsNone(result) + + def test_modify_io_limit_rule_invalid_id(self): + """ + Test modify io_limit_rule with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.provisioning.modify_io_limit_rule, + self.data.invalid_io_limit_rule_id, + {"max_iops": 10000}, + ) + + def test_modify_io_limit_rule_uses_patch(self): + """ + Test modify io_limit_rule uses PATCH method + + Validates that mock_request is called with PATCH and the rule object URL + """ + payload = {"max_bw": 204800} + with mock.patch.object(self.provisioning.client, "request") as mock_request: + mock_request.return_value = None + self.provisioning.modify_io_limit_rule(self.data.io_limit_rule_id1, payload) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.PATCH) + self.assertIn(self.data.io_limit_rule_id1, args[1]) + + def test_delete_io_limit_rule(self): + """ + Test delete io_limit_rule + + Validates that delete returns None (204 No Content) + """ + result = self.provisioning.delete_io_limit_rule(self.data.io_limit_rule_id1) + self.assertIsNone(result) + + def test_delete_io_limit_rule_invalid_id(self): + """ + Test delete io_limit_rule with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.provisioning.delete_io_limit_rule, + self.data.invalid_io_limit_rule_id, + ) + + def test_delete_io_limit_rule_uses_delete(self): + """ + Test delete io_limit_rule uses DELETE method + + Validates that mock_request is called with DELETE and the rule object URL + """ + with mock.patch.object(self.provisioning.client, "request") as mock_request: + mock_request.return_value = None + self.provisioning.delete_io_limit_rule(self.data.io_limit_rule_id1) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.DELETE) + self.assertIn(self.data.io_limit_rule_id1, args[1]) + + def test_get_io_limit_rule_details_query_select(self): + """ + Test get io_limit_rule details uses IO_LIMIT_RULE_DETAILS_QUERY + + Validates that the querystring includes the correct select fields + """ + with mock.patch.object(self.provisioning.client, "request") as mock_request: + mock_request.return_value = self.data.io_limit_rule1 + self.provisioning.get_io_limit_rule_details(self.data.io_limit_rule_id1) + _, kwargs = mock_request.call_args + self.assertEqual( + kwargs.get("querystring"), constants.IO_LIMIT_RULE_DETAILS_QUERY, + ) + + def test_get_io_limit_rule_details_object_url(self): + """ + Test get io_limit_rule details uses IO_LIMIT_RULE_OBJECT_URL + + Validates that the URL contains the rule ID + """ + with mock.patch.object(self.provisioning.client, "request") as mock_request: + mock_request.return_value = self.data.io_limit_rule1 + self.provisioning.get_io_limit_rule_details(self.data.io_limit_rule_id1) + args, _ = mock_request.call_args + expected_url = constants.IO_LIMIT_RULE_OBJECT_URL.format( + self.provisioning.server_ip, self.data.io_limit_rule_id1, + ) + self.assertEqual(args[1], expected_url) diff --git a/PyPowerStore/tests/unit_tests/test_qos_policy.py b/PyPowerStore/tests/unit_tests/test_qos_policy.py new file mode 100644 index 0000000..d356d11 --- /dev/null +++ b/PyPowerStore/tests/unit_tests/test_qos_policy.py @@ -0,0 +1,249 @@ +"""Unit tests for QoS / File_Performance Policy""" + +from unittest import mock + +from PyPowerStore.tests.unit_tests.base_test import TestBase +from PyPowerStore.utils import constants +from PyPowerStore.utils.exception import PowerStoreException + + +class TestQosPolicy(TestBase): + """ + Unit tests for QoS / File_Performance Policy SDK methods on protection. + """ + + def test_get_policy_details_qos(self): + """ + Test get_policy_details for a QoS policy + + Validates that returned details equal self.data.qos_policy1 + """ + result = self.protection.get_policy_details(self.data.qos_policy_id1) + self.assertEqual(result, self.data.qos_policy1) + + def test_get_policy_details_file_performance(self): + """ + Test get_policy_details for a File_Performance policy + + Validates that returned details equal self.data.file_perf_policy1 + """ + result = self.protection.get_policy_details(self.data.qos_policy_id2) + self.assertEqual(result, self.data.file_perf_policy1) + + def test_get_policy_by_name(self): + """ + Test get_policy_by_name + + Validates that returned list equals [self.data.qos_policy1] + """ + result = self.protection.get_policy_by_name(self.data.qos_policy_name1) + self.assertEqual(result, [self.data.qos_policy1]) + + def test_get_policy_by_name_uses_list_url(self): + """ + Test get_policy_by_name uses PROTECTION_POLICY_LIST_URL + + Validates that mock_request is called with the list URL and name filter + """ + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = [self.data.qos_policy1] + self.protection.get_policy_by_name(self.data.qos_policy_name1) + args, _ = mock_request.call_args + self.assertEqual( + args[1], + constants.PROTECTION_POLICY_LIST_URL.format( + self.provisioning.server_ip, + ), + ) + + def test_get_policy_details_uses_qos_query(self): + """ + Test get_policy_details uses QOS_POLICY_DETAILS_QUERY + + Validates the querystring contains the QoS select fields + """ + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = self.data.qos_policy1 + self.protection.get_policy_details(self.data.qos_policy_id1) + _, kwargs = mock_request.call_args + self.assertEqual( + kwargs.get("querystring"), constants.QOS_POLICY_DETAILS_QUERY, + ) + + def test_get_policy_details_uses_object_url(self): + """ + Test get_policy_details uses PROTECTION_POLICY_OBJECT_URL + + Validates that the URL contains the policy ID + """ + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = self.data.qos_policy1 + self.protection.get_policy_details(self.data.qos_policy_id1) + args, _ = mock_request.call_args + expected_url = constants.PROTECTION_POLICY_OBJECT_URL.format( + self.provisioning.server_ip, self.data.qos_policy_id1, + ) + self.assertEqual(args[1], expected_url) + + def test_create_policy_qos(self): + """ + Test create_policy for QoS type + + Validates that returned dict contains the expected policy ID + """ + payload = { + "name": self.data.qos_policy_name1, + "io_limit_rule_id": self.data.io_limit_rule_id1, + } + result = self.protection.create_policy(payload) + self.assertEqual(result.get("id"), self.data.qos_policy_id1) + + def test_create_policy_file_performance(self): + """ + Test create_policy for File_Performance type + + Validates that mock_request is called with POST and the list URL + """ + payload = { + "name": self.data.qos_policy_name2, + "file_io_limit_rule_id": self.data.file_io_limit_rule_id1, + } + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = {"id": self.data.qos_policy_id2} + self.protection.create_policy(payload) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.POST) + self.assertEqual( + args[1], + constants.PROTECTION_POLICY_LIST_URL.format( + self.provisioning.server_ip, + ), + ) + + def test_create_policy_uses_post(self): + """ + Test create_policy uses POST method + + Validates that mock_request is called with POST + """ + payload = {"name": self.data.qos_policy_name1, "io_limit_rule_id": self.data.io_limit_rule_id1} + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = {"id": self.data.qos_policy_id1} + self.protection.create_policy(payload) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.POST) + + def test_modify_policy(self): + """ + Test modify_policy + + Validates that modify returns None (204 No Content) + """ + payload = {"description": "Updated Gold QoS policy"} + result = self.protection.modify_policy(self.data.qos_policy_id1, payload) + self.assertIsNone(result) + + def test_modify_policy_invalid_id(self): + """ + Test modify_policy with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.protection.modify_policy, + self.data.invalid_qos_policy_id, + {"description": "bad"}, + ) + + def test_modify_policy_uses_patch(self): + """ + Test modify_policy uses PATCH method + + Validates that mock_request is called with PATCH and the object URL + """ + payload = {"io_limit_rule_id": self.data.io_limit_rule_id2} + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = None + self.protection.modify_policy(self.data.qos_policy_id1, payload) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.PATCH) + self.assertIn(self.data.qos_policy_id1, args[1]) + + def test_modify_policy_uses_object_url(self): + """ + Test modify_policy uses PROTECTION_POLICY_OBJECT_URL + + Validates that the URL contains the policy ID + """ + payload = {"description": "test"} + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = None + self.protection.modify_policy(self.data.qos_policy_id1, payload) + args, _ = mock_request.call_args + expected_url = constants.PROTECTION_POLICY_OBJECT_URL.format( + self.provisioning.server_ip, self.data.qos_policy_id1, + ) + self.assertEqual(args[1], expected_url) + + def test_delete_policy(self): + """ + Test delete_policy + + Validates that delete returns None (204 No Content) + """ + result = self.protection.delete_policy(self.data.qos_policy_id1) + self.assertIsNone(result) + + def test_delete_policy_invalid_id(self): + """ + Test delete_policy with invalid ID + + Validates that PowerStoreException is raised + """ + self.assertRaises( + PowerStoreException, + self.protection.delete_policy, + self.data.invalid_qos_policy_id, + ) + + def test_delete_policy_uses_delete(self): + """ + Test delete_policy uses DELETE method + + Validates that mock_request is called with DELETE and the object URL + """ + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = None + self.protection.delete_policy(self.data.qos_policy_id1) + args, _ = mock_request.call_args + self.assertEqual(args[0], constants.DELETE) + self.assertIn(self.data.qos_policy_id1, args[1]) + + def test_delete_policy_uses_object_url(self): + """ + Test delete_policy uses PROTECTION_POLICY_OBJECT_URL + + Validates the URL contains the policy ID + """ + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = None + self.protection.delete_policy(self.data.qos_policy_id1) + args, _ = mock_request.call_args + expected_url = constants.PROTECTION_POLICY_OBJECT_URL.format( + self.provisioning.server_ip, self.data.qos_policy_id1, + ) + self.assertEqual(args[1], expected_url) + + def test_get_policy_by_name_query_includes_io_limit_rule(self): + """ + Test get_policy_by_name querystring includes io_limit_rule select + + Validates that the querystring select field contains 'io_limit_rule' + """ + with mock.patch.object(self.protection.rest_client, "request") as mock_request: + mock_request.return_value = [self.data.qos_policy1] + self.protection.get_policy_by_name(self.data.qos_policy_name1) + _, kwargs = mock_request.call_args + qs = kwargs.get("querystring", {}) + self.assertIn("io_limit_rule", qs.get("select", "")) diff --git a/PyPowerStore/utils/constants.py b/PyPowerStore/utils/constants.py index b70a7a8..ec4127e 100644 --- a/PyPowerStore/utils/constants.py +++ b/PyPowerStore/utils/constants.py @@ -517,6 +517,23 @@ LDAP_ACCOUNT_DETAILS_QUERY = {"select": "id,role_id,domain_id,name,type,type_l10n,dn"} # Select all Snapshot +# IO Limit Rule query +IO_LIMIT_RULE_DETAILS_QUERY = { + "select": "id,name,max_bw,max_iops,burst_percentage,type,type_l10n,policies(id,name)", +} + +# File IO Limit Rule query +FILE_IO_LIMIT_RULE_DETAILS_QUERY = { + "select": "id,name,max_bw,policies(id,name)", +} + +# QoS / File_Performance Policy query +QOS_POLICY_DETAILS_QUERY = { + "select": "id,name,description,type,type_l10n," + "io_limit_rules(id,name)," + "file_io_limit_rules(id,name)", +} + EQUALS = "eq." # API endpoints @@ -623,6 +640,18 @@ PROTECTION_POLICY_LIST_URL = "https://{0}/api/rest/policy" PROTECTION_POLICY_OBJECT_URL = "https://{0}/api/rest/policy/{1}" +# IO Limit Rule endpoints (v4.0.0.0+) +IO_LIMIT_RULE_LIST_URL = "https://{0}/api/rest/io_limit_rule" +IO_LIMIT_RULE_OBJECT_URL = "https://{0}/api/rest/io_limit_rule/{1}" + +# File IO Limit Rule endpoints (v4.1.0.0+) +FILE_IO_LIMIT_RULE_LIST_URL = "https://{0}/api/rest/file_io_limit_rule" +FILE_IO_LIMIT_RULE_OBJECT_URL = "https://{0}/api/rest/file_io_limit_rule/{1}" + +# QoS Policy endpoints (reuses PROTECTION_POLICY_LIST/OBJECT URLs - same /policy endpoint) +QOS_POLICY_TYPE_FILTER = {"type": EQUALS + "QoS"} +FILE_PERFORMANCE_POLICY_TYPE_FILTER = {"type": EQUALS + "File_Performance"} + # Host Volume Mapping endpoints HOST_VOLUME_MAPPING_URL = "https://{0}/api/rest/host_volume_mapping" From 3abfc7b7ce63f75333c2ee2159d8c459ff5b9b6c Mon Sep 17 00:00:00 2001 From: Rounak Adhikary Date: Thu, 21 May 2026 16:07:42 +0530 Subject: [PATCH 2/3] Fixing more --- PyPowerStore/protection.py | 6 +- PyPowerStore/provisioning.py | 58 ++++++++++++++++--- .../tests/unit_tests/test_qos_policy.py | 15 +++-- PyPowerStore/utils/constants.py | 6 ++ 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/PyPowerStore/protection.py b/PyPowerStore/protection.py index ddd12e1..ee7b092 100644 --- a/PyPowerStore/protection.py +++ b/PyPowerStore/protection.py @@ -861,7 +861,8 @@ def get_policy_details(self, policy_id): # Use singular field names: io_limit_rule, file_io_limit_rule # The API reference incorrectly shows plural forms querystring = { - "select": "id,name,description,type,type_l10n,io_limit_rule(id,name),file_io_limit_rule(id,name)", + "select": "id,name,description,type,type_l10n," + "io_limit_rule(id,name),file_io_limit_rule(id,name)", } return self.rest_client.request( constants.GET, @@ -882,7 +883,8 @@ def get_policy_by_name(self, name, policy_type=None): LOG.info("Getting QoS policy details by name: '%s'", name) # Use singular field names: io_limit_rule, file_io_limit_rule querystring = { - "select": "id,name,description,type,type_l10n,io_limit_rule(id,name),file_io_limit_rule(id,name)", + "select": "id,name,description,type,type_l10n," + "io_limit_rule(id,name),file_io_limit_rule(id,name)", "name": constants.EQUALS + name, } if policy_type: diff --git a/PyPowerStore/provisioning.py b/PyPowerStore/provisioning.py index aaf32d8..b7de967 100644 --- a/PyPowerStore/provisioning.py +++ b/PyPowerStore/provisioning.py @@ -82,6 +82,7 @@ def create_volume( volume_group_id=None, protection_policy_id=None, performance_policy_id=None, + qos_performance_policy_id=None, app_type=None, app_type_other=None, appliance_id=None, @@ -94,6 +95,7 @@ def create_volume( :param volume_group_id: (optional) The volume group ID :param protection_policy_id: (optional) The protection policy ID :param performance_policy_id: (optional) The performance policy ID + :param qos_performance_policy_id: (optional) The QoS performance policy ID :param app_type: (optional) The application type :param app_type_other: (optional) Describes application type when app_type is set to other @@ -113,6 +115,7 @@ def create_volume( volume_group_id, protection_policy_id, performance_policy_id, + qos_performance_policy_id, app_type, app_type_other, appliance_id, @@ -129,6 +132,7 @@ def _prepare_create_volume_payload( volume_group_id, protection_policy_id, performance_policy_id, + qos_performance_policy_id, app_type, app_type_other, appliance_id, @@ -146,6 +150,8 @@ def _prepare_create_volume_payload( create_volume_dict["protection_policy_id"] = protection_policy_id if performance_policy_id is not None: create_volume_dict["performance_policy_id"] = performance_policy_id + if qos_performance_policy_id is not None: + create_volume_dict["qos_performance_policy_id"] = qos_performance_policy_id if app_type is not None: create_volume_dict["app_type"] = app_type if app_type_other is not None: @@ -178,12 +184,13 @@ def modify_volume( size=None, protection_policy_id=None, performance_policy_id=None, + qos_performance_policy_id=None, app_type=None, app_type_other=None, ): """Modify a volume. - :param volume_id: The volume ID + :param volume_id: The Volume ID :type volume_id: str :param name: The name of the volume :type name: str @@ -195,6 +202,8 @@ def modify_volume( :type protection_policy_id: str :param performance_policy_id: The performance policy ID :type performance_policy_id: str + :param qos_performance_policy_id: The QoS performance policy ID + :type qos_performance_policy_id: str :param app_type: The application type :type app_type: str :param app_type_other: Describes application type when @@ -215,6 +224,7 @@ def modify_volume( size, protection_policy_id, performance_policy_id, + qos_performance_policy_id, app_type, app_type_other, ) @@ -231,6 +241,7 @@ def _prepare_modify_volume_payload( size=None, protection_policy_id=None, performance_policy_id=None, + qos_performance_policy_id=None, app_type=None, app_type_other=None, ): @@ -245,6 +256,8 @@ def _prepare_modify_volume_payload( modify_volume_dict["protection_policy_id"] = protection_policy_id if performance_policy_id is not None: modify_volume_dict["performance_policy_id"] = performance_policy_id + if qos_performance_policy_id is not None: + modify_volume_dict["qos_performance_policy_id"] = qos_performance_policy_id if app_type is not None: modify_volume_dict["app_type"] = app_type if app_type_other is not None: @@ -262,6 +275,7 @@ def clone_volume( logical_unit_number=None, protection_policy_id=None, performance_policy_id=None, + qos_performance_policy_id=None, ): """Clone a volume. @@ -281,6 +295,8 @@ def clone_volume( :type protection_policy_id: str :param performance_policy_id: The performance policy ID :type performance_policy_id: str + :param qos_performance_policy_id: The QoS performance policy ID + :type qos_performance_policy_id: str :return: 'id' Unique identifier of the new clone volume if success else raise exception :rtype: dict """ @@ -293,6 +309,7 @@ def clone_volume( logical_unit_number, protection_policy_id, performance_policy_id, + qos_performance_policy_id, ) return self.client.request( constants.POST, @@ -309,6 +326,7 @@ def _prepare_clone_volume_payload( logical_unit_number=None, protection_policy_id=None, performance_policy_id=None, + qos_performance_policy_id=None, ): clone_volume_dict = {} if name is not None: @@ -325,6 +343,8 @@ def _prepare_clone_volume_payload( clone_volume_dict["protection_policy_id"] = protection_policy_id if performance_policy_id is not None: clone_volume_dict["performance_policy_id"] = performance_policy_id + if qos_performance_policy_id is not None: + clone_volume_dict["qos_performance_policy_id"] = qos_performance_policy_id return clone_volume_dict @@ -1384,6 +1404,7 @@ def create_volume_group( volume_ids=None, is_write_order_consistent=None, protection_policy_id=None, + qos_performance_policy_id=None, ): """Create a volume group. @@ -1401,6 +1422,8 @@ def create_volume_group( protection policy to assign to the volume group. :type protection_policy_id: str + :param qos_performance_policy_id: (optional) The QoS performance policy ID + :type qos_performance_policy_id: str :return: Volume ID if success else raise exception :rtype: dict """ @@ -1411,6 +1434,7 @@ def create_volume_group( volume_ids, is_write_order_consistent, protection_policy_id, + qos_performance_policy_id, ) return self.client.request( constants.POST, @@ -1420,6 +1444,7 @@ def create_volume_group( def clone_volume_group( self, volume_group_id, name, description=None, protection_policy_id=None, + qos_performance_policy_id=None, ): """Clone a volume group. @@ -1429,16 +1454,23 @@ def clone_volume_group( :type name: str :param description: (optional) Description for the clone volume group. :type description: str - :param protection_policy_id: (optional) Unique identifier of the protection - policy to assign to the clone volume group + :param protection_policy_id: (optional) Protection policy ID for clone. :type protection_policy_id: str - :return: Unique identifier of the new instance created if success else raise exception + :param qos_performance_policy_id: (optional) QoS performance policy ID for clone. + :type qos_performance_policy_id: str + :return: ID of the cloned volume group if success else raise exception :rtype: dict """ - LOG.info( "Cloning volumegroup: '%s'", volume_group_id) - payload = self._prepare_clone_vg_payload( - name, description, protection_policy_id, - ) + LOG.info("Cloning volume group: '%s'", volume_group_id) + payload = {} + if name is not None: + payload["name"] = name + if description is not None: + payload["description"] = description + if protection_policy_id is not None: + payload["protection_policy_id"] = protection_policy_id + if qos_performance_policy_id is not None: + payload["qos_performance_policy_id"] = qos_performance_policy_id return self.client.request( constants.POST, constants.CLONE_VOLUME_GROUP_URL.format(self.server_ip, volume_group_id), @@ -1541,6 +1573,7 @@ def _prepare_create_vg_payload( volume_ids, is_write_order_consistent, protection_policy_id, + qos_performance_policy_id, ): create_volume_group_dict = {} if name is not None: @@ -1555,6 +1588,8 @@ def _prepare_create_vg_payload( ) if protection_policy_id is not None: create_volume_group_dict["protection_policy_id"] = protection_policy_id + if qos_performance_policy_id is not None: + create_volume_group_dict["qos_performance_policy_id"] = qos_performance_policy_id return create_volume_group_dict @@ -1602,6 +1637,7 @@ def modify_volume_group( description=None, is_write_order_consistent=None, protection_policy_id=None, + qos_performance_policy_id=None, ): """Modify a volume group. @@ -1621,10 +1657,13 @@ def modify_volume_group( is specified, protection policy will be removed from the volume group. :type protection_policy_id: str + :param qos_performance_policy_id: (optional) The QoS performance policy ID + :type qos_performance_policy_id: str """ LOG.info( "Modifying volumegroup: '%s'", volume_group_id) payload = self._prepare_modify_vg_payload( name, description, is_write_order_consistent, protection_policy_id, + qos_performance_policy_id, ) self.client.request( constants.PATCH, @@ -1634,6 +1673,7 @@ def modify_volume_group( def _prepare_modify_vg_payload( self, name, description, is_write_order_consistent, protection_policy_id, + qos_performance_policy_id, ): modify_vg_dict = {} if name is not None: @@ -1644,6 +1684,8 @@ def _prepare_modify_vg_payload( modify_vg_dict["is_write_order_consistent"] = is_write_order_consistent if protection_policy_id is not None: modify_vg_dict["protection_policy_id"] = protection_policy_id + if qos_performance_policy_id is not None: + modify_vg_dict["qos_performance_policy_id"] = qos_performance_policy_id return modify_vg_dict diff --git a/PyPowerStore/tests/unit_tests/test_qos_policy.py b/PyPowerStore/tests/unit_tests/test_qos_policy.py index d356d11..86a5558 100644 --- a/PyPowerStore/tests/unit_tests/test_qos_policy.py +++ b/PyPowerStore/tests/unit_tests/test_qos_policy.py @@ -58,7 +58,7 @@ def test_get_policy_by_name_uses_list_url(self): def test_get_policy_details_uses_qos_query(self): """ - Test get_policy_details uses QOS_POLICY_DETAILS_QUERY + Test get_policy_details uses correct querystring Validates the querystring contains the QoS select fields """ @@ -66,9 +66,11 @@ def test_get_policy_details_uses_qos_query(self): mock_request.return_value = self.data.qos_policy1 self.protection.get_policy_details(self.data.qos_policy_id1) _, kwargs = mock_request.call_args - self.assertEqual( - kwargs.get("querystring"), constants.QOS_POLICY_DETAILS_QUERY, - ) + expected_query = { + "select": "id,name,description,type,type_l10n," + "io_limit_rule(id,name),file_io_limit_rule(id,name)" + } + self.assertEqual(kwargs.get("querystring"), expected_query) def test_get_policy_details_uses_object_url(self): """ @@ -126,7 +128,10 @@ def test_create_policy_uses_post(self): Validates that mock_request is called with POST """ - payload = {"name": self.data.qos_policy_name1, "io_limit_rule_id": self.data.io_limit_rule_id1} + payload = { + "name": self.data.qos_policy_name1, + "io_limit_rule_id": self.data.io_limit_rule_id1 + } with mock.patch.object(self.protection.rest_client, "request") as mock_request: mock_request.return_value = {"id": self.data.qos_policy_id1} self.protection.create_policy(payload) diff --git a/PyPowerStore/utils/constants.py b/PyPowerStore/utils/constants.py index ec4127e..92e7929 100644 --- a/PyPowerStore/utils/constants.py +++ b/PyPowerStore/utils/constants.py @@ -129,6 +129,8 @@ "timestamp," "is_protectable, protection_policy_id," "protection_policy(name,id)," + "qos_performance_policy_id," + "qos_performance_policy(id,name)," "migration_session_id," "is_write_order_consistent," "placement_rule,type," @@ -166,6 +168,7 @@ "access_policy_l10n, locking_policy_l10n," "folder_rename_policy_l10n, access_type_l10n," "creator_type_l10n,is_secure,nas_server(name,id)," + "performance_policy_id," "protection_policy(name,id)", } @@ -187,6 +190,7 @@ "access_policy_l10n, locking_policy_l10n," "folder_rename_policy_l10n, access_type_l10n," "creator_type_l10n,nas_server(name,id)," + "performance_policy_id," "protection_policy(name,id)," "file_events_publishing_mode," "file_events_publishing_mode_l10n," @@ -216,6 +220,7 @@ "current_preferred_IPv4_interface_id," "current_preferred_IPv6_interface_id," "protection_policy_id," + "performance_policy_id," "operational_status_l10n," "current_unix_directory_service_l10n," "file_interfaces(name,id,ip_address)," @@ -243,6 +248,7 @@ "current_preferred_IPv6_interface_id," "operational_status_l10n," "current_unix_directory_service_l10n," + "performance_policy_id," "file_interfaces(name,id,ip_address)," "nfs_servers,smb_servers," "file_ldaps,file_nises,file_systems(id,name)", From be2edcf0e54bcd879994f80588ac7fa5b322d7bd Mon Sep 17 00:00:00 2001 From: Rounak Adhikary Date: Thu, 21 May 2026 17:41:27 +0530 Subject: [PATCH 3/3] Fix pylint errors in QoS implementation - Add docstrings to all methods in file_io_limit_rule.py entity - Add docstrings to all methods in io_limit_rule.py entity - Add docstrings to QoS methods in policy.py entity - Fix line-too-long errors in common_data.py f-strings - Fix unused variable warning in test_io_limit_rule.py --- .../tests/unit_tests/data/common_data.py | 10 +++- .../unit_tests/entity/file_io_limit_rule.py | 50 +++++++++++++++++++ .../tests/unit_tests/entity/io_limit_rule.py | 50 +++++++++++++++++++ .../tests/unit_tests/entity/policy.py | 25 ++++++++++ .../tests/unit_tests/test_io_limit_rule.py | 2 +- 5 files changed, 134 insertions(+), 3 deletions(-) diff --git a/PyPowerStore/tests/unit_tests/data/common_data.py b/PyPowerStore/tests/unit_tests/data/common_data.py index 863b319..9a26117 100644 --- a/PyPowerStore/tests/unit_tests/data/common_data.py +++ b/PyPowerStore/tests/unit_tests/data/common_data.py @@ -384,7 +384,10 @@ class CommonData: { "arguments": [invalid_io_limit_rule_id], "code": "0xE0A090010001", - "message_l10n": f"Unable to find the IO limit rule with ID {invalid_io_limit_rule_id}", + "message_l10n": ( + f"Unable to find the IO limit rule with ID " + f"{invalid_io_limit_rule_id}" + ), "severity": "Error", }, ], @@ -425,7 +428,10 @@ class CommonData: { "arguments": [invalid_file_io_limit_rule_id], "code": "0xE0A090010001", - "message_l10n": f"Unable to find the file IO limit rule with ID {invalid_file_io_limit_rule_id}", + "message_l10n": ( + f"Unable to find the file IO limit rule with ID " + f"{invalid_file_io_limit_rule_id}" + ), "severity": "Error", }, ], diff --git a/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py b/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py index 10d0dc5..f507cea 100644 --- a/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py +++ b/PyPowerStore/tests/unit_tests/entity/file_io_limit_rule.py @@ -16,6 +16,13 @@ class FileIoLimitRuleResponse(Entity): """ def __init__(self, method, url, **kwargs): + """Initialize FileIoLimitRuleResponse. + + Args: + method: HTTP method + url: Request URL + **kwargs: Additional request parameters + """ self.method = method self.url = url self.kwargs = kwargs @@ -23,6 +30,11 @@ def __init__(self, method, url, **kwargs): self.status_code = 200 def get_api_name(self): + """Return the API method name based on HTTP method and URL. + + Returns: + The corresponding API method + """ if self.method == "GET": if self.url.endswith("/file_io_limit_rule"): if self.kwargs.get("params", {}).get("name"): @@ -38,29 +50,67 @@ def get_api_name(self): return None def execute_api(self, api_name): + """Execute the given API method. + + Args: + api_name: The API method to execute + + Returns: + Tuple of status code and response + """ status_code, response = api_name() return status_code, response def get_file_io_limit_rules(self): + """Get all file IO limit rules. + + Returns: + Tuple of status code and rule list + """ return self.status_code, self.data.file_io_limit_rule_list def get_file_io_limit_rule_by_name(self): + """Get file IO limit rule by name. + + Returns: + Tuple of status code and rule + """ return self.status_code, [self.data.file_io_limit_rule1] def get_file_io_limit_rule_details(self): + """Get file IO limit rule details by ID. + + Returns: + Tuple of status code and rule details + """ if self.data.invalid_file_io_limit_rule_id in self.url: return 404, self.data.file_io_limit_rule_error[404] return self.status_code, self.data.file_io_limit_rule1 def create_file_io_limit_rule(self): + """Create a new file IO limit rule. + + Returns: + Tuple of status code and created rule ID + """ return 201, {"id": self.data.file_io_limit_rule_id1} def modify_file_io_limit_rule(self): + """Modify an existing file IO limit rule. + + Returns: + Tuple of status code and response + """ if self.data.invalid_file_io_limit_rule_id in self.url: return 404, self.data.file_io_limit_rule_error[404] return 204, None def delete_file_io_limit_rule(self): + """Delete a file IO limit rule. + + Returns: + Tuple of status code and response + """ if self.data.invalid_file_io_limit_rule_id in self.url: return 404, self.data.file_io_limit_rule_error[404] return 204, None diff --git a/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py b/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py index 6b8ec8f..7d8ee58 100644 --- a/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py +++ b/PyPowerStore/tests/unit_tests/entity/io_limit_rule.py @@ -16,6 +16,13 @@ class IoLimitRuleResponse(Entity): """ def __init__(self, method, url, **kwargs): + """Initialize IoLimitRuleResponse. + + Args: + method: HTTP method + url: Request URL + **kwargs: Additional request parameters + """ self.method = method self.url = url self.kwargs = kwargs @@ -23,6 +30,11 @@ def __init__(self, method, url, **kwargs): self.status_code = 200 def get_api_name(self): + """Return the API method name based on HTTP method and URL. + + Returns: + The corresponding API method + """ if self.method == "GET": if self.url.endswith("/io_limit_rule"): if self.kwargs.get("params", {}).get("name"): @@ -38,29 +50,67 @@ def get_api_name(self): return None def execute_api(self, api_name): + """Execute the given API method. + + Args: + api_name: The API method to execute + + Returns: + Tuple of status code and response + """ status_code, response = api_name() return status_code, response def get_io_limit_rules(self): + """Get all IO limit rules. + + Returns: + Tuple of status code and rule list + """ return self.status_code, self.data.io_limit_rule_list def get_io_limit_rule_by_name(self): + """Get IO limit rule by name. + + Returns: + Tuple of status code and rule + """ return self.status_code, [self.data.io_limit_rule1] def get_io_limit_rule_details(self): + """Get IO limit rule details by ID. + + Returns: + Tuple of status code and rule details + """ if self.data.invalid_io_limit_rule_id in self.url: return 404, self.data.io_limit_rule_error[404] return self.status_code, self.data.io_limit_rule1 def create_io_limit_rule(self): + """Create a new IO limit rule. + + Returns: + Tuple of status code and created rule ID + """ return 201, {"id": self.data.io_limit_rule_id1} def modify_io_limit_rule(self): + """Modify an existing IO limit rule. + + Returns: + Tuple of status code and response + """ if self.data.invalid_io_limit_rule_id in self.url: return 404, self.data.io_limit_rule_error[404] return 204, None def delete_io_limit_rule(self): + """Delete an IO limit rule. + + Returns: + Tuple of status code and response + """ if self.data.invalid_io_limit_rule_id in self.url: return 404, self.data.io_limit_rule_error[404] return 204, None diff --git a/PyPowerStore/tests/unit_tests/entity/policy.py b/PyPowerStore/tests/unit_tests/entity/policy.py index 8239eb0..9f5fcaf 100644 --- a/PyPowerStore/tests/unit_tests/entity/policy.py +++ b/PyPowerStore/tests/unit_tests/entity/policy.py @@ -159,17 +159,37 @@ def delete_protection_policy(self): return 204, None def get_qos_policy_by_name(self): + """Get QoS policy by name. + + Returns: + Tuple of status code and policy + """ return self.status_code, [self.data.qos_policy1] def get_qos_policy_details(self): + """Get QoS policy details by ID. + + Returns: + Tuple of status code and policy details + """ if self.data.qos_policy_id2 in self.url: return self.status_code, self.data.file_perf_policy1 return self.status_code, self.data.qos_policy1 def create_qos_policy(self): + """Create a new QoS policy. + + Returns: + Tuple of status code and created policy ID + """ return 201, {"id": self.data.qos_policy_id1} def modify_qos_policy(self): + """Modify an existing QoS policy. + + Returns: + Tuple of status code and modified policy + """ if self.data.invalid_qos_policy_id in self.url: return 404, self.data.qos_policy_error[404] if self.data.qos_policy_id1 not in self.url and self.data.qos_policy_id2 not in self.url: @@ -177,6 +197,11 @@ def modify_qos_policy(self): return 204, self.data.qos_policy1_modified def delete_qos_policy(self): + """Delete a QoS policy. + + Returns: + Tuple of status code and response + """ if self.data.invalid_qos_policy_id in self.url: return 404, self.data.qos_policy_error[404] if self.data.qos_policy_id1 not in self.url and self.data.qos_policy_id2 not in self.url: diff --git a/PyPowerStore/tests/unit_tests/test_io_limit_rule.py b/PyPowerStore/tests/unit_tests/test_io_limit_rule.py index 6794c5b..6e100b6 100644 --- a/PyPowerStore/tests/unit_tests/test_io_limit_rule.py +++ b/PyPowerStore/tests/unit_tests/test_io_limit_rule.py @@ -77,7 +77,7 @@ def test_get_io_limit_rule_by_name_uses_correct_url(self): with mock.patch.object(self.provisioning.client, "request") as mock_request: mock_request.return_value = [self.data.io_limit_rule1] self.provisioning.get_io_limit_rule_by_name(self.data.io_limit_rule_name1) - args, kwargs = mock_request.call_args + args, _ = mock_request.call_args self.assertIn( constants.IO_LIMIT_RULE_LIST_URL.format(self.provisioning.server_ip), args,