From 5d2781324c0b0898a4512954167ad3a2ff5ace2c Mon Sep 17 00:00:00 2001 From: Tao He Date: Mon, 4 Aug 2025 15:55:09 +0000 Subject: [PATCH 1/3] Implememt device, snapshot_policy and device_group --- PyPowerFlex/__init__.py | 4 + PyPowerFlex/objects/gen2/__init__.py | 6 + PyPowerFlex/objects/gen2/device.py | 198 +++++++++++++++ PyPowerFlex/objects/gen2/device_group.py | 151 ++++++++++++ PyPowerFlex/objects/gen2/snapshot_policy.py | 139 +++++++++++ tests/gen2/test_device.py | 241 ++++++++++++++++++ tests/gen2/test_device_group.py | 183 ++++++++++++++ tests/gen2/test_snapshot_policy.py | 258 ++++++++++++++++++++ 8 files changed, 1180 insertions(+) create mode 100644 PyPowerFlex/objects/gen2/device.py create mode 100644 PyPowerFlex/objects/gen2/device_group.py create mode 100644 PyPowerFlex/objects/gen2/snapshot_policy.py create mode 100644 tests/gen2/test_device.py create mode 100644 tests/gen2/test_device_group.py create mode 100644 tests/gen2/test_snapshot_policy.py diff --git a/PyPowerFlex/__init__.py b/PyPowerFlex/__init__.py index 630d001..cd9ff40 100644 --- a/PyPowerFlex/__init__.py +++ b/PyPowerFlex/__init__.py @@ -65,6 +65,7 @@ class PowerFlexClient: 'host', # gen2 'storage_node', + 'device_group', ) def __init__(self, @@ -159,3 +160,6 @@ def add_objects_gen2(self): self.__add_storage_entity('storage_node', gen2.StorageNode) self.__add_storage_entity('protection_domain', gen2.ProtectionDomain) self.__add_storage_entity('storage_pool', gen2.StoragePool) + self.__add_storage_entity('snapshot_policy', gen2.SnapshotPolicy) + self.__add_storage_entity('device', gen2.Device) + self.__add_storage_entity('device_group', gen2.DeviceGroup) diff --git a/PyPowerFlex/objects/gen2/__init__.py b/PyPowerFlex/objects/gen2/__init__.py index 7ed0cd3..36a9002 100644 --- a/PyPowerFlex/objects/gen2/__init__.py +++ b/PyPowerFlex/objects/gen2/__init__.py @@ -18,9 +18,15 @@ from PyPowerFlex.objects.gen2.storage_node import StorageNode from PyPowerFlex.objects.gen2.protection_domain import ProtectionDomain from PyPowerFlex.objects.gen2.storage_pool import StoragePool +from PyPowerFlex.objects.gen2.snapshot_policy import SnapshotPolicy +from PyPowerFlex.objects.gen2.device import Device +from PyPowerFlex.objects.gen2.device_group import DeviceGroup __all__ = [ 'StorageNode', 'ProtectionDomain', 'StoragePool', + 'SnapshotPolicy', + 'Device', + 'DeviceGroup', ] diff --git a/PyPowerFlex/objects/gen2/device.py b/PyPowerFlex/objects/gen2/device.py new file mode 100644 index 0000000..2c69712 --- /dev/null +++ b/PyPowerFlex/objects/gen2/device.py @@ -0,0 +1,198 @@ +# Copyright (c) 2025 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Module for interacting with device APIs.""" + +# pylint: disable=too-few-public-methods,too-many-arguments,too-many-positional-arguments,no-member,duplicate-code + +import logging + +import requests + +from PyPowerFlex import base_client +from PyPowerFlex import exceptions + + +LOG = logging.getLogger(__name__) + + +class MediaType: + """Device media types.""" + ssd = 'SSD' + pmem = 'PMEM' + + +class Device(base_client.EntityRequest): + """ + A class representing Device client. + """ + + def create(self, + current_pathname, + device_group_id, + node_id, + force=None, + media_type=None, + name=None): + """Create PowerFlex device. + + :type current_pathname: str + :type device_group_id: str + :type node_id: str + :type force: bool + :param media_type: one of predefined attributes of MediaType + :type media_type: str + :type name: str + :rtype: dict + """ + + if not all([current_pathname, device_group_id, node_id]): + msg = 'current_pathname, device_group_id and node_id must be set.' + raise exceptions.InvalidInput(msg) + + params = { + "deviceCurrentPathname": current_pathname, + "deviceGroupId": device_group_id, + "nodeId": node_id, + "forceDeviceTakeover": force, + "mediaType": media_type, + "name": name + } + + return self._create_entity(params) + + def delete(self, device_id): + """Remove PowerFlex device. + + :type device_id: str + :rtype: None + """ + + return self._delete_entity(device_id) + + def rename(self, device_id, name): + """Rename PowerFlex device. + + :type device_id: str + :type name: str + :rtype: dict + """ + + action = 'setDeviceName' + + params = { + "newName": name + } + + return self._rename_entity(action, device_id, params) + + # TODO TTHE make sure this API is valid after the latest dev build is ready + def update_pathname(self, device_id, new_pathname): + """Update PowerFlex device pathname. + + :type device_id: str + :type new_pathname: str + :rtype: dict + """ + + action = 'updateDeviceOriginalPathname' + params = {"updateDeviceOriginalPathname": new_pathname} + r, response = self.send_post_request(self.base_action_url, + action=action, + entity=self.entity, + entity_id=device_id, + params=params) + if r.status_code != requests.codes.ok: + msg = ( + f"Failed to update pathname for PowerFlex {self.entity} " + f"with id {device_id}. " + f"Error: {response}" + ) + LOG.error(msg) + raise exceptions.PowerFlexClientException(msg) + + return self.get(entity_id=device_id) + + # TODO TTHE make sure this API is valid after the latest dev build is ready + def set_capacity_limit(self, device_id, capacity_limit_gb): + """Update PowerFlex device capacity limit in GB. + + :type device_id: str + :type capacity_limit_gb: int + :rtype: dict + """ + + action = 'setDeviceCapacityLimit' + params = {"capacityLimitInGB": capacity_limit_gb} + r, response = self.send_post_request(self.base_action_url, + action=action, + entity=self.entity, + entity_id=device_id, + params=params) + if r.status_code != requests.codes.ok: + msg = ( + f"Failed to set capacity limit for PowerFlex {self.entity} " + f"with id {device_id}. " + f"Error: {response}" + ) + LOG.error(msg) + raise exceptions.PowerFlexClientException(msg) + + return self.get(entity_id=device_id) + + def clear_errors(self, device_id, force=None): + """Clear PowerFlex device errors. + + :type device_id: str + :rtype: dict + """ + + action = 'clearDeviceError' + + # TODO TTHE make sure this field is valid after the latest dev build is ready + params = {"forceClear": force} + + return self._perform_entity_operation_based_on_action( + action=action, + entity_id=device_id, + params=params, + add_entity=False) + + def activate(self, device_id, node_id): + """Activate PowerFlex device. + + :type device_id: str + :type node_id: str + :rtype: dict + """ + + action = 'activateDevice' + + params = {"storageNodeId": node_id} + + return self._perform_entity_operation_based_on_action( + action=action, + entity_id=device_id, + params=params, + add_entity=False) + + def query_metrics(self, device_id, metrics=None): + """Query PowerFlex Metrics for device. + + :type device_id: str + :type metrics: list|tuple + :rtype: dict + """ + return super().query_metrics('device', [device_id], metrics) \ No newline at end of file diff --git a/PyPowerFlex/objects/gen2/device_group.py b/PyPowerFlex/objects/gen2/device_group.py new file mode 100644 index 0000000..a0451c4 --- /dev/null +++ b/PyPowerFlex/objects/gen2/device_group.py @@ -0,0 +1,151 @@ +# Copyright (c) 2025 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Module for interacting with device group APIs.""" + +# pylint: disable=too-few-public-methods,too-many-arguments,too-many-positional-arguments,no-member,duplicate-code + +import logging + +import requests + +from PyPowerFlex import base_client +from PyPowerFlex import exceptions + + +LOG = logging.getLogger(__name__) + + +class MediaType: + """Device Group media types.""" + ssd = 'SSD' + pmem = 'PMEM' + + +class DeviceGroup(base_client.EntityRequest): + """ + A class representing Device Group client. + """ + def create(self, + name, + protection_domain_id, + media_type, + spare_node_count=None, + spare_device_count=None): + """Create PowerFlex device group. + + :type protection_domain_id: str + :param media_type: one of predefined attributes of MediaType + :type media_type: str + :type name: str + :type spare_node_count: int + :type spare_device_count: int + :rtype: dict + """ + + if not all([name, protection_domain_id, media_type]): + msg = 'name, protection_domain_id and media_type must be set.' + raise exceptions.InvalidInput(msg) + + params = { + "dgName": name, + "mediaType": media_type, + "protectionDomainId": protection_domain_id, + "spareNodeCount": spare_node_count, + "spareDeviceCount": spare_device_count + } + + return self._create_entity(params) + + def delete(self, device_group_id, force=None): + """Remove PowerFlex device group. + + :type device_group_id: str + :type force: bool + :rtype: None + """ + # TODO TTHE make sure this `force` field is valid after the latest dev build is ready + params = { + "force": force + } + + return self._delete_entity(device_group_id, params) + + def modify(self, + device_group_id, + new_name=None, + spare_node_count=None, + spare_device_count=None): + """Modify PowerFlex device group. + + :type new_name: str + :type spare_node_count: int + :type spare_device_count: int + :rtype: None + """ + + action = 'modifyDeviceGroup' + + params = { + "newName": new_name, + "spareNodeCount": spare_node_count, + "spareDeviceCount": spare_device_count + } + r, response = self.send_post_request(self.base_action_url, + action=action, + entity=self.entity, + entity_id=device_group_id, + params=params) + if r.status_code != requests.codes.ok: + msg = ( + f"Failed to modify PowerFlex {self.entity} with id {device_group_id}. " + f"Error: {response}" + ) + LOG.error(msg) + raise exceptions.PowerFlexClientException(msg) + + return self.get(entity_id=device_group_id) + + def query_usable_capacity(self, device_group_id): + """Query PowerFlex device group usable capacity. + + :type device_group_id: str + :rtype: dict + """ + + action = 'queryUsableCapacity' + r, response = self.send_post_request(self.base_action_url, + action=action, + entity=self.entity, + entity_id=device_group_id) + if r.status_code != requests.codes.ok: + msg = ( + f"Failed to query usable capacity for PowerFlex {self.entity} " + f"with id {device_group_id}. " + f"Error: {response}" + ) + LOG.error(msg) + raise exceptions.PowerFlexClientException(msg) + + return response + + def query_metrics(self, device_group_id, metrics=None): + """Query PowerFlex Metrics for device group. + + :type device_group_id: str + :type metrics: list|tuple + :rtype: dict + """ + return super().query_metrics('device_group', [device_group_id], metrics) diff --git a/PyPowerFlex/objects/gen2/snapshot_policy.py b/PyPowerFlex/objects/gen2/snapshot_policy.py new file mode 100644 index 0000000..102ec6d --- /dev/null +++ b/PyPowerFlex/objects/gen2/snapshot_policy.py @@ -0,0 +1,139 @@ +# Copyright (c) 2025 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Module for interacting with snapshot policy APIs in PowerFlex 5.x.""" + +# pylint: disable=too-few-public-methods,no-member,too-many-arguments,too-many-positional-arguments,duplicate-code + +import logging + +import requests + +from PyPowerFlex import exceptions +from PyPowerFlex.objects.gen1.snapshot_policy import SnapshotPolicy as SnapshotPolicyGen1 + + +LOG = logging.getLogger(__name__) + + +class AutoSnapshotRemovalAction: + """Auto snapshot deletion strategy.""" + + detach = 'Detach' + remove = 'Remove' + + +class SnapshotPolicy(SnapshotPolicyGen1): + """ + A class representing Snapshot Policy client. + """ + def add_source_volume(self, snapshot_policy_id, volume_id): + """Assign source volume to PowerFlex snapshot policy. + + :type snapshot_policy_id: str + :type volume_id: str + :rtype: dict + """ + + action = 'assignSnapshotPolicy' + + params = {"sourceVolumeId": volume_id} + + r, response = self.send_post_request(self.base_action_url, + action=action, + entity=self.entity, + entity_id=snapshot_policy_id, + params=params) + if r.status_code != requests.codes.ok: + msg = ( + f"Failed to assign source volume {volume_id} to PowerFlex {self.entity} " + f"with id {snapshot_policy_id}. " + f"Error: {response}" + ) + LOG.error(msg) + raise exceptions.PowerFlexClientException(msg) + + return self.get(entity_id=snapshot_policy_id) + + def remove_source_volume(self, + snapshot_policy_id, + volume_id, + auto_snap_removal_action, + detach_locked_auto_snaps=None): + """Unassign source volume from PowerFlex snapshot policy. + + :type snapshot_policy_id: str + :type volume_id: str + :param auto_snap_removal_action: one of predefined attributes of + AutoSnapshotRemovalAction + :type auto_snap_removal_action: str + :type detach_locked_auto_snaps: bool + :rtype: dict + """ + + action = 'unassignSnapshotPolicy' + + params = { + "sourceVolumeId": volume_id, + "autoSnapshotRemovalAction": auto_snap_removal_action, + "detachLockedAutoSnapshots": detach_locked_auto_snaps + } + + r, response = self.send_post_request(self.base_action_url, + action=action, + entity=self.entity, + entity_id=snapshot_policy_id, + params=params) + if r.status_code != requests.codes.ok: + msg = ( + f"Failed to unassign source volume {volume_id} from PowerFlex {self.entity} " + f"with id {snapshot_policy_id}. " + f"Error: {response}" + ) + LOG.error(msg) + raise exceptions.PowerFlexClientException(msg) + + return self.get(entity_id=snapshot_policy_id) + + # TODO TTHE make sure this API is valid after new dev build is ready + def query_metrics(self, snapshot_policy_id, metrics=None): + """Query PowerFlex Metrics for snapshot policy. + + :type snapshot_policy_id: str + :type metrics: list|tuple + :rtype: dict + """ + return super().query_metrics('snapshot_policy', [snapshot_policy_id], metrics) + + def get_statistics(self, snapshot_policy_id, fields=None): + """Get PowerFlex Snapshot Policy Statistics not supported in PowerFlex 5.x. + + :type snapshot_policy_id: str + :type fields: list|tuple + :rtype: dict + """ + LOG.error("Get PowerFlex Snapshot Policy Statistics not supported in PowerFlex 5.x.") + return None + + def query_selected_statistics(self, properties, ids=None): + """Query PowerFlex snapshot policy statistics not supported in PowerFlex 5.x. + + :type properties: list + :type ids: list of snapshot policy IDs or None for all snapshot + policies + :rtype: dict + """ + LOG.error("Query PowerFlex snapshot policy statistics not supported in PowerFlex 5.x.") + return None diff --git a/tests/gen2/test_device.py b/tests/gen2/test_device.py new file mode 100644 index 0000000..ff60ef1 --- /dev/null +++ b/tests/gen2/test_device.py @@ -0,0 +1,241 @@ +# Copyright (c) 2025 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Module for testing device client.""" + +# pylint: disable=invalid-name + +from PyPowerFlex import exceptions +from PyPowerFlex.objects.gen2.device import MediaType +from tests.common import PyPowerFlexTestCase + + +@PyPowerFlexTestCase.version('5.0') +class TestDeviceClient(PyPowerFlexTestCase): + """ + Test class for DeviceClient. + """ + def setUp(self): + """ + Set up the test environment. + """ + super().setUp() + self.client.initialize() + self.fake_device_id = '1' + self.fake_device_name = '1' + self.fake_device_group_id = '1' + self.fake_node_id = '1' + + self.MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + '/types/Device/instances': + {'id': self.fake_device_id}, + f'/instances/Device::{self.fake_device_id}': + {'id': self.fake_device_id}, + f'/instances/Device::{self.fake_device_id}' + '/action/removeDevice': + {}, + f'/instances/Device::{self.fake_device_id}' + '/action/setDeviceName': + {}, + f'/instances/Device::{self.fake_device_id}' + '/action/setDeviceCapacityLimit': # TODO TTHE update this API after the latest build is ready + {}, + f'/instances/Device::{self.fake_device_id}' + '/action/updateDeviceOriginalPathname': # TODO TTHE update this API after the latest build is ready + {}, + f'/instances/Device::{self.fake_device_id}' + '/action/clearDeviceError': + {}, + f'/instances/Device::{self.fake_device_id}' + '/action/activateDevice': + {}, + '/dtapi/rest/v1/metrics/query': { + self.fake_device_id: {'raw_total': 1099511627776} + }, + }, + self.RESPONSE_MODE.Invalid: { + '/types/Device/instances': + {}, + } + } + + def test_device_create(self): + """ + Test device creation. + """ + self.client.device.create(current_pathname='/dev/sda', + device_group_id=self.fake_device_group_id, + node_id=self.fake_node_id, + media_type=MediaType.ssd, + name=self.fake_device_name) + + def test_device_create_bad_status(self): + """ + Test device creation with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailCreating, + self.client.device.create, + current_pathname='/dev/sda', + device_group_id=self.fake_device_group_id, + node_id=self.fake_node_id, + media_type=MediaType.ssd, + name=self.fake_device_name) + + def test_device_create_no_id_in_response(self): + """ + Test device creation with no id in response. + """ + with self.http_response_mode(self.RESPONSE_MODE.Invalid): + self.assertRaises(KeyError, + self.client.device.create, + current_pathname='/dev/sda', + device_group_id=self.fake_device_group_id, + node_id=self.fake_node_id, + media_type=MediaType.ssd, + name=self.fake_device_name) + + def test_device_create_node_id_invalid_input(self): + """ + Test device creation with invalid node id set. + """ + with self.http_response_mode(self.RESPONSE_MODE.Invalid): + self.assertRaises(exceptions.InvalidInput, + self.client.device.create, + current_pathname='/dev/sda', + device_group_id=self.fake_device_group_id, + media_type=MediaType.ssd, + node_id=None, + name=self.fake_device_name) + + def test_device_delete(self): + """ + Test device deletion. + """ + self.client.device.delete(self.fake_device_id) + + def test_device_delete_bad_status(self): + """ + Test device deletion with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailDeleting, + self.client.device.delete, + self.fake_device_id) + + def test_device_rename(self): + """ + Test device renaming. + """ + self.client.device.rename(self.fake_device_id, name='new_name') + + def test_device_rename_bad_status(self): + """ + Test device renaming with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailRenaming, + self.client.device.rename, + self.fake_device_id, + name='new_name') + + def test_device_update_pathname(self): + """ + Test device update_pathname. + """ + self.client.device.update_pathname( + self.fake_device_id, + new_pathname='/dev/sdb') + + def test_device_update_pathname_bad_status(self): + """ + Test device update_pathname with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.device.update_pathname, + self.fake_device_id, + new_pathname='/dev/sdb') + + def test_device_set_capacity_limit(self): + """ + Test device set_capacity_limit. + """ + self.client.device.set_capacity_limit( + self.fake_device_id, + capacity_limit_gb=500) + + def test_device_set_capacity_limit_bad_status(self): + """ + Test device set_capacity_limit with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.device.set_capacity_limit, + self.fake_device_id, + capacity_limit_gb=500) + + def test_device_clear_errors(self): + """ + Test device clear_errors. + """ + self.client.device.clear_errors(self.fake_device_id) + + def test_device_clear_errors_bad_status(self): + """ + Test device clear_errors with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.device.clear_errors, + self.fake_device_id) + + def test_device_activate(self): + """ + Test device activate. + """ + self.client.device.activate(self.fake_device_id, self.fake_node_id) + + def test_device_activate_bad_status(self): + """ + Test device activate with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.device.clear_errors, + self.fake_device_id, + self.fake_node_id) + + def test_device_query_metrics(self): + """ + Test device query selected metrics. + """ + ret = self.client.device.query_metrics(self.fake_device_id) + assert ret.get(self.fake_device_id).get("raw_total") == 1099511627776 + + def test_device_query_metrics_bad_status(self): + """ + Test device query selected metrics with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.device.query_metrics, + self.fake_device_id) diff --git a/tests/gen2/test_device_group.py b/tests/gen2/test_device_group.py new file mode 100644 index 0000000..e12c060 --- /dev/null +++ b/tests/gen2/test_device_group.py @@ -0,0 +1,183 @@ +# Copyright (c) 2025 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Module for testing device group client.""" + +# pylint: disable=invalid-name + +from PyPowerFlex import exceptions +from PyPowerFlex.objects.gen2.device_group import MediaType +from tests.common import PyPowerFlexTestCase + + +@PyPowerFlexTestCase.version('5.0') +class TestDeviceClient(PyPowerFlexTestCase): + """ + Test class for DeviceGroupClient. + """ + def setUp(self): + """ + Set up the test environment. + """ + super().setUp() + self.client.initialize() + self.fake_device_group_id = '1' + self.fake_device_group_name = '1' + self.fake_protection_domain_id = '1' + + self.MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + '/types/DeviceGroup/instances': + {'id': self.fake_device_group_id}, + f'/instances/DeviceGroup::{self.fake_device_group_id}': + {'id': self.fake_device_group_id}, + f'/instances/DeviceGroup::{self.fake_device_group_id}' + '/action/removeDeviceGroup': + {}, + f'/instances/DeviceGroup::{self.fake_device_group_id}' + '/action/modifyDeviceGroup': + {}, + f'/instances/DeviceGroup::{self.fake_device_group_id}' + '/action/queryUsableCapacity':{ + self.fake_device_group_id: {'numProtectionSlices': 2} + }, + '/dtapi/rest/v1/metrics/query': { + self.fake_device_group_id: {'physical_total': 10995116277760} + }, + }, + self.RESPONSE_MODE.Invalid: { + '/types/DeviceGroup/instances': + {}, + } + } + + def test_device_group_create(self): + """ + Test device creation. + """ + self.client.device_group.create(name=self.fake_device_group_name, + protection_domain_id=self.fake_protection_domain_id, + media_type=MediaType.ssd, + spare_device_count=1, + spare_node_count=1) + + def test_device_group_create_bad_status(self): + """ + Test device creation with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailCreating, + self.client.device_group.create, + name=self.fake_device_group_name, + protection_domain_id=self.fake_protection_domain_id, + media_type=MediaType.ssd, + spare_device_count=1, + spare_node_count=1) + + def test_device_group_create_no_id_in_response(self): + """ + Test device creation with no id in response. + """ + with self.http_response_mode(self.RESPONSE_MODE.Invalid): + self.assertRaises(KeyError, + self.client.device_group.create, + name=self.fake_device_group_name, + protection_domain_id=self.fake_protection_domain_id, + media_type=MediaType.ssd, + spare_device_count=1, + spare_node_count=1) + + def test_device_group_create_name_invalid_input(self): + """ + Test device creation with invalid name set. + """ + with self.http_response_mode(self.RESPONSE_MODE.Invalid): + self.assertRaises(exceptions.InvalidInput, + self.client.device_group.create, + name=None, + protection_domain_id=self.fake_protection_domain_id, + media_type=MediaType.ssd, + spare_device_count=1, + spare_node_count=1) + + def test_device_group_delete(self): + """ + Test device deletion. + """ + self.client.device_group.delete(self.fake_device_group_id) + + def test_device_group_delete_bad_status(self): + """ + Test device deletion with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailDeleting, + self.client.device_group.delete, + self.fake_device_group_id) + + def test_device_group_modify(self): + """ + Test device modify. + """ + self.client.device_group.modify(self.fake_device_group_id, + new_name="new_name", + spare_device_count=1, + spare_node_count=1) + + def test_device_group_modify_bad_status(self): + """ + Test device modify with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexClientException, + self.client.device_group.modify, + self.fake_device_group_id, + new_name="new_name", + spare_device_count=1, + spare_node_count=1) + + def test_device_group_query_metrics(self): + """ + Test device group query selected metrics. + """ + ret = self.client.device_group.query_metrics(self.fake_device_group_id) + assert ret.get(self.fake_device_group_id).get("physical_total") == 10995116277760 + + def test_device_group_query_metrics_bad_status(self): + """ + Test device group query selected metrics with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexFailQuerying, + self.client.device_group.query_metrics, + self.fake_device_group_id) + + def test_device_group_query_usable_capacity(self): + """ + Test device group query_usable_capacity method. + """ + ret = self.client.device_group.query_usable_capacity(self.fake_device_group_id) + assert ret.get(self.fake_device_group_id).get("numProtectionSlices") == 2 + + def test_device_group_query_metrics_bad_status(self): + """ + Test device group query_usable_capacity with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.device_group.query_usable_capacity, + self.fake_device_group_id) diff --git a/tests/gen2/test_snapshot_policy.py b/tests/gen2/test_snapshot_policy.py new file mode 100644 index 0000000..526e78f --- /dev/null +++ b/tests/gen2/test_snapshot_policy.py @@ -0,0 +1,258 @@ +# Copyright (c) 2025 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Module for testing snapshot policy client.""" + +# pylint: disable=invalid-name + +from PyPowerFlex import exceptions +from PyPowerFlex.objects.gen2 import snapshot_policy as sp +from tests.common import PyPowerFlexTestCase + + +@PyPowerFlexTestCase.version('5.0') +class TestSnapshotPolicyClient(PyPowerFlexTestCase): + """ + Test class for snapshot policy client. + """ + def setUp(self): + """ + Set up the test case. + """ + super().setUp() + self.client.initialize() + self.fake_policy_id = '1' + self.fake_volume_id = '1' + + self.MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + '/types/SnapshotPolicy/instances': + {'id': self.fake_policy_id}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}': + {'id': self.fake_policy_id}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/removeSnapshotPolicy': + {}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/assignSnapshotPolicy': + {}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/modifySnapshotPolicy': + {}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/pauseSnapshotPolicy': + {}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/unassignSnapshotPolicy': + {}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/renameSnapshotPolicy': + {}, + f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/resumeSnapshotPolicy': + {}, + '/dtapi/rest/v1/metrics/query': { + # TODO TTHE update this API after the latest build is ready + self.fake_policy_id: {'numOfpypowerflexVols': 1} + }, + }, + self.RESPONSE_MODE.Invalid: { + '/types/SnapshotPolicy/instances': + {}, + } + } + + def test_snapshot_policy_add_source_volume(self): + """ + Test adding a source volume to a snapshot policy. + """ + self.client.snapshot_policy.add_source_volume(self.fake_policy_id, + self.fake_volume_id) + + def test_snapshot_policy_add_source_volume_bad_status(self): + """ + Test adding a source volume to a snapshot policy with a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexClientException, + self.client.snapshot_policy.add_source_volume, + self.fake_policy_id, + self.fake_volume_id) + + def test_snapshot_policy_create(self): + """ + Test creating a snapshot policy. + """ + self.client.snapshot_policy.create( + auto_snap_creation_cadence_in_min=15, + retained_snaps_per_level=[1, 2, 3], + name='policy_1', + paused=False) + + def test_snapshot_policy_create_bad_status(self): + """ + Test creating a snapshot policy with a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailCreating, + self.client.snapshot_policy.create, + auto_snap_creation_cadence_in_min=15, + retained_snaps_per_level=[1, 2, 3], + name='policy_1', + paused=False) + + def test_snapshot_policy_create_no_id_in_response(self): + """ + Test creating a snapshot policy with no id in the response. + """ + with self.http_response_mode(self.RESPONSE_MODE.Invalid): + self.assertRaises(KeyError, + self.client.snapshot_policy.create, + auto_snap_creation_cadence_in_min=15, + retained_snaps_per_level=[1, 2, 3], + name='policy_1', + paused=False) + + def test_snapshot_policy_delete(self): + """ + Test deleting a snapshot policy. + """ + self.client.snapshot_policy.delete(self.fake_policy_id) + + def test_snapshot_policy_delete_bad_status(self): + """ + Test deleting a snapshot policy with a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailDeleting, + self.client.snapshot_policy.delete, + self.fake_policy_id) + + def test_snapshot_policy_modify(self): + """ + Test modifying a snapshot policy. + """ + self.client.snapshot_policy.modify( + self.fake_policy_id, + auto_snap_creation_cadence_in_min=25, + retained_snaps_per_level=[1, 2, 4] + ) + + def test_snapshot_policy_modify_bad_status(self): + """ + Test modifying a snapshot policy with a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexClientException, + self.client.snapshot_policy.modify, + self.fake_policy_id, + auto_snap_creation_cadence_in_min=25, + retained_snaps_per_level=[1, 2, 4]) + + def test_snapshot_policy_pause(self): + """ + Test pausing a snapshot policy. + """ + self.client.snapshot_policy.pause(self.fake_policy_id) + + def test_snapshot_policy_pause_bad_status(self): + """ + Test pausing a snapshot policy with a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexClientException, + self.client.snapshot_policy.pause, + self.fake_policy_id) + + def test_snapshot_policy_remove_source_volume(self): + """ + Test removing a source volume from a snapshot policy. + """ + self.client.snapshot_policy.remove_source_volume( + self.fake_policy_id, + self.fake_volume_id, + auto_snap_removal_action=sp.AutoSnapshotRemovalAction.detach, + detach_locked_auto_snaps=True) + + def test_snapshot_policy_remove_source_volume_bad_status(self): + """ + Test removing a source volume from a snapshot policy with a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexClientException, + self.client.snapshot_policy.remove_source_volume, + self.fake_policy_id, + self.fake_volume_id, + sp.AutoSnapshotRemovalAction.remove, + False) + + def test_snapshot_policy_rename(self): + """ + Test renaming a snapshot policy. + """ + self.client.snapshot_policy.rename(self.fake_policy_id, + name='new_name') + + def test_snapshot_policy_rename_bad_status(self): + """ + Tests the behavior of the rename method when the HTTP response has a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexFailRenaming, + self.client.snapshot_policy.rename, + self.fake_policy_id, + name='new_name') + + def test_snapshot_policy_resume(self): + """ + Tests the behavior of the resume method. + """ + self.client.snapshot_policy.resume(self.fake_policy_id) + + def test_snapshot_policy_resume_bad_status(self): + """ + Tests the behavior of the resume method when the HTTP response has a bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises(exceptions.PowerFlexClientException, + self.client.snapshot_policy.resume, + self.fake_policy_id) + + def test_snapshot_policy_query_metrics(self): + """ + Test snapshot policy query selected metrics. + """ + ret = self.client.snapshot_policy.query_metrics(self.fake_policy_id) + assert ret.get(self.fake_policy_id).get("numOfpypowerflexVols") == 1 + + def test_snapshot_policy_query_metrics_bad_status(self): + """ + Test snapshot policy query selected metrics with bad status. + """ + with self.http_response_mode(self.RESPONSE_MODE.BadStatus): + self.assertRaises( + exceptions.PowerFlexClientException, + self.client.snapshot_policy.query_metrics, + self.fake_policy_id) + + def test_snapshot_policy_query_selected_statistics_not_supported(self): + """ + Tests the behavior of the query_selected_statistics method. + """ + ret = self.client.snapshot_policy.query_selected_statistics( + properties=["numOfpypowerflexVols"]) + assert ret is None + + def test_snapshot_policy_get_statistics_not_supported(self): + """ + Tests the behavior of the get_statistics method. + """ + ret = self.client.snapshot_policy.get_statistics( + snapshot_policy_id=self.fake_policy_id, + fields=["numOfpypowerflexVols"]) + assert ret is None From 1897d16dd52ea3c68f40544e989eff898f0cdd0d Mon Sep 17 00:00:00 2001 From: Tao He Date: Mon, 4 Aug 2025 16:29:32 +0000 Subject: [PATCH 2/3] Fix lint issues --- PyPowerFlex/objects/gen2/device.py | 12 ++++++------ PyPowerFlex/objects/gen2/device_group.py | 8 ++++---- PyPowerFlex/objects/gen2/snapshot_policy.py | 8 +++----- tests/gen2/test_device.py | 18 +++++++++++------- tests/gen2/test_device_group.py | 10 +++++----- tests/gen2/test_snapshot_policy.py | 6 +++--- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/PyPowerFlex/objects/gen2/device.py b/PyPowerFlex/objects/gen2/device.py index 2c69712..aa133c4 100644 --- a/PyPowerFlex/objects/gen2/device.py +++ b/PyPowerFlex/objects/gen2/device.py @@ -98,9 +98,9 @@ def rename(self, device_id, name): return self._rename_entity(action, device_id, params) - # TODO TTHE make sure this API is valid after the latest dev build is ready def update_pathname(self, device_id, new_pathname): """Update PowerFlex device pathname. + TODO TTHE make sure this API is valid after the latest dev build is ready :type device_id: str :type new_pathname: str @@ -125,9 +125,9 @@ def update_pathname(self, device_id, new_pathname): return self.get(entity_id=device_id) - # TODO TTHE make sure this API is valid after the latest dev build is ready def set_capacity_limit(self, device_id, capacity_limit_gb): """Update PowerFlex device capacity limit in GB. + TODO TTHE make sure this API is valid after the latest dev build is ready :type device_id: str :type capacity_limit_gb: int @@ -154,6 +154,7 @@ def set_capacity_limit(self, device_id, capacity_limit_gb): def clear_errors(self, device_id, force=None): """Clear PowerFlex device errors. + TODO TTHE make sure this field - `forceClear` is valid after the latest dev build is ready :type device_id: str :rtype: dict @@ -161,7 +162,6 @@ def clear_errors(self, device_id, force=None): action = 'clearDeviceError' - # TODO TTHE make sure this field is valid after the latest dev build is ready params = {"forceClear": force} return self._perform_entity_operation_based_on_action( @@ -169,7 +169,7 @@ def clear_errors(self, device_id, force=None): entity_id=device_id, params=params, add_entity=False) - + def activate(self, device_id, node_id): """Activate PowerFlex device. @@ -188,11 +188,11 @@ def activate(self, device_id, node_id): params=params, add_entity=False) - def query_metrics(self, device_id, metrics=None): + def query_device_metrics(self, device_id, metrics=None): """Query PowerFlex Metrics for device. :type device_id: str :type metrics: list|tuple :rtype: dict """ - return super().query_metrics('device', [device_id], metrics) \ No newline at end of file + return self.query_metrics('device', [device_id], metrics) diff --git a/PyPowerFlex/objects/gen2/device_group.py b/PyPowerFlex/objects/gen2/device_group.py index a0451c4..5eaab40 100644 --- a/PyPowerFlex/objects/gen2/device_group.py +++ b/PyPowerFlex/objects/gen2/device_group.py @@ -41,7 +41,7 @@ class DeviceGroup(base_client.EntityRequest): def create(self, name, protection_domain_id, - media_type, + media_type, spare_node_count=None, spare_device_count=None): """Create PowerFlex device group. @@ -71,12 +71,12 @@ def create(self, def delete(self, device_group_id, force=None): """Remove PowerFlex device group. + TODO TTHE make sure this `force` field is valid after the latest dev build is ready :type device_group_id: str :type force: bool :rtype: None """ - # TODO TTHE make sure this `force` field is valid after the latest dev build is ready params = { "force": force } @@ -141,11 +141,11 @@ def query_usable_capacity(self, device_group_id): return response - def query_metrics(self, device_group_id, metrics=None): + def query_device_group_metrics(self, device_group_id, metrics=None): """Query PowerFlex Metrics for device group. :type device_group_id: str :type metrics: list|tuple :rtype: dict """ - return super().query_metrics('device_group', [device_group_id], metrics) + return self.query_metrics('device_group', [device_group_id], metrics) diff --git a/PyPowerFlex/objects/gen2/snapshot_policy.py b/PyPowerFlex/objects/gen2/snapshot_policy.py index 102ec6d..1de8351 100644 --- a/PyPowerFlex/objects/gen2/snapshot_policy.py +++ b/PyPowerFlex/objects/gen2/snapshot_policy.py @@ -107,15 +107,15 @@ def remove_source_volume(self, return self.get(entity_id=snapshot_policy_id) - # TODO TTHE make sure this API is valid after new dev build is ready - def query_metrics(self, snapshot_policy_id, metrics=None): + def query_snapshot_policy_metrics(self, snapshot_policy_id, metrics=None): """Query PowerFlex Metrics for snapshot policy. + TODO TTHE make sure this API is valid after new dev build is ready :type snapshot_policy_id: str :type metrics: list|tuple :rtype: dict """ - return super().query_metrics('snapshot_policy', [snapshot_policy_id], metrics) + return self.query_metrics('snapshot_policy', [snapshot_policy_id], metrics) def get_statistics(self, snapshot_policy_id, fields=None): """Get PowerFlex Snapshot Policy Statistics not supported in PowerFlex 5.x. @@ -125,7 +125,6 @@ def get_statistics(self, snapshot_policy_id, fields=None): :rtype: dict """ LOG.error("Get PowerFlex Snapshot Policy Statistics not supported in PowerFlex 5.x.") - return None def query_selected_statistics(self, properties, ids=None): """Query PowerFlex snapshot policy statistics not supported in PowerFlex 5.x. @@ -136,4 +135,3 @@ def query_selected_statistics(self, properties, ids=None): :rtype: dict """ LOG.error("Query PowerFlex snapshot policy statistics not supported in PowerFlex 5.x.") - return None diff --git a/tests/gen2/test_device.py b/tests/gen2/test_device.py index ff60ef1..7189917 100644 --- a/tests/gen2/test_device.py +++ b/tests/gen2/test_device.py @@ -15,7 +15,7 @@ """Module for testing device client.""" -# pylint: disable=invalid-name +# pylint: disable=invalid-name,duplicate-code from PyPowerFlex import exceptions from PyPowerFlex.objects.gen2.device import MediaType @@ -51,11 +51,15 @@ def setUp(self): '/action/setDeviceName': {}, f'/instances/Device::{self.fake_device_id}' - '/action/setDeviceCapacityLimit': # TODO TTHE update this API after the latest build is ready - {}, + '/action/setDeviceCapacityLimit': + { + # TODO TTHE update this API after the latest build is ready + }, f'/instances/Device::{self.fake_device_id}' - '/action/updateDeviceOriginalPathname': # TODO TTHE update this API after the latest build is ready - {}, + '/action/updateDeviceOriginalPathname': + { + # TODO TTHE update this API after the latest build is ready + }, f'/instances/Device::{self.fake_device_id}' '/action/clearDeviceError': {}, @@ -227,7 +231,7 @@ def test_device_query_metrics(self): """ Test device query selected metrics. """ - ret = self.client.device.query_metrics(self.fake_device_id) + ret = self.client.device.query_device_metrics(self.fake_device_id) assert ret.get(self.fake_device_id).get("raw_total") == 1099511627776 def test_device_query_metrics_bad_status(self): @@ -237,5 +241,5 @@ def test_device_query_metrics_bad_status(self): with self.http_response_mode(self.RESPONSE_MODE.BadStatus): self.assertRaises( exceptions.PowerFlexClientException, - self.client.device.query_metrics, + self.client.device.query_device_metrics, self.fake_device_id) diff --git a/tests/gen2/test_device_group.py b/tests/gen2/test_device_group.py index e12c060..acaa108 100644 --- a/tests/gen2/test_device_group.py +++ b/tests/gen2/test_device_group.py @@ -15,7 +15,7 @@ """Module for testing device group client.""" -# pylint: disable=invalid-name +# pylint: disable=invalid-name,duplicate-code from PyPowerFlex import exceptions from PyPowerFlex.objects.gen2.device_group import MediaType @@ -152,7 +152,7 @@ def test_device_group_query_metrics(self): """ Test device group query selected metrics. """ - ret = self.client.device_group.query_metrics(self.fake_device_group_id) + ret = self.client.device_group.query_device_group_metrics(self.fake_device_group_id) assert ret.get(self.fake_device_group_id).get("physical_total") == 10995116277760 def test_device_group_query_metrics_bad_status(self): @@ -161,8 +161,8 @@ def test_device_group_query_metrics_bad_status(self): """ with self.http_response_mode(self.RESPONSE_MODE.BadStatus): self.assertRaises( - exceptions.PowerFlexFailQuerying, - self.client.device_group.query_metrics, + exceptions.PowerFlexClientException, + self.client.device_group.query_device_group_metrics, self.fake_device_group_id) def test_device_group_query_usable_capacity(self): @@ -172,7 +172,7 @@ def test_device_group_query_usable_capacity(self): ret = self.client.device_group.query_usable_capacity(self.fake_device_group_id) assert ret.get(self.fake_device_group_id).get("numProtectionSlices") == 2 - def test_device_group_query_metrics_bad_status(self): + def test_device_group_query_usable_capacity_bad_status(self): """ Test device group query_usable_capacity with bad status. """ diff --git a/tests/gen2/test_snapshot_policy.py b/tests/gen2/test_snapshot_policy.py index 526e78f..d22acfd 100644 --- a/tests/gen2/test_snapshot_policy.py +++ b/tests/gen2/test_snapshot_policy.py @@ -15,7 +15,7 @@ """Module for testing snapshot policy client.""" -# pylint: disable=invalid-name +# pylint: disable=invalid-name,too-many-public-methods,duplicate-code from PyPowerFlex import exceptions from PyPowerFlex.objects.gen2 import snapshot_policy as sp @@ -227,7 +227,7 @@ def test_snapshot_policy_query_metrics(self): """ Test snapshot policy query selected metrics. """ - ret = self.client.snapshot_policy.query_metrics(self.fake_policy_id) + ret = self.client.snapshot_policy.query_snapshot_policy_metrics(self.fake_policy_id) assert ret.get(self.fake_policy_id).get("numOfpypowerflexVols") == 1 def test_snapshot_policy_query_metrics_bad_status(self): @@ -237,7 +237,7 @@ def test_snapshot_policy_query_metrics_bad_status(self): with self.http_response_mode(self.RESPONSE_MODE.BadStatus): self.assertRaises( exceptions.PowerFlexClientException, - self.client.snapshot_policy.query_metrics, + self.client.snapshot_policy.query_snapshot_policy_metrics, self.fake_policy_id) def test_snapshot_policy_query_selected_statistics_not_supported(self): From fc8058f6a13cdf96042a0891c1f17c95d8532127 Mon Sep 17 00:00:00 2001 From: Tao He Date: Mon, 4 Aug 2025 16:40:43 +0000 Subject: [PATCH 3/3] Fix lint issues --- tests/gen2/test_device.py | 8 ++------ tests/gen2/test_snapshot_policy.py | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/gen2/test_device.py b/tests/gen2/test_device.py index 7189917..39bb0e7 100644 --- a/tests/gen2/test_device.py +++ b/tests/gen2/test_device.py @@ -52,14 +52,10 @@ def setUp(self): {}, f'/instances/Device::{self.fake_device_id}' '/action/setDeviceCapacityLimit': - { - # TODO TTHE update this API after the latest build is ready - }, + {}, f'/instances/Device::{self.fake_device_id}' '/action/updateDeviceOriginalPathname': - { - # TODO TTHE update this API after the latest build is ready - }, + {}, f'/instances/Device::{self.fake_device_id}' '/action/clearDeviceError': {}, diff --git a/tests/gen2/test_snapshot_policy.py b/tests/gen2/test_snapshot_policy.py index d22acfd..142cb36 100644 --- a/tests/gen2/test_snapshot_policy.py +++ b/tests/gen2/test_snapshot_policy.py @@ -57,7 +57,6 @@ def setUp(self): f'/instances/SnapshotPolicy::{self.fake_policy_id}/action/resumeSnapshotPolicy': {}, '/dtapi/rest/v1/metrics/query': { - # TODO TTHE update this API after the latest build is ready self.fake_policy_id: {'numOfpypowerflexVols': 1} }, },