Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions PyPowerFlex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class PowerFlexClient:
'host',
# gen2
'storage_node',
'device_group',
)

def __init__(self,
Expand Down Expand Up @@ -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)
6 changes: 6 additions & 0 deletions PyPowerFlex/objects/gen2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
198 changes: 198 additions & 0 deletions PyPowerFlex/objects/gen2/device.py
Original file line number Diff line number Diff line change
@@ -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)

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
: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)

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
: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.
TODO TTHE make sure this field - `forceClear` is valid after the latest dev build is ready

:type device_id: str
:rtype: dict
"""

action = 'clearDeviceError'

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_device_metrics(self, device_id, metrics=None):
"""Query PowerFlex Metrics for device.

:type device_id: str
:type metrics: list|tuple
:rtype: dict
"""
return self.query_metrics('device', [device_id], metrics)
151 changes: 151 additions & 0 deletions PyPowerFlex/objects/gen2/device_group.py
Original file line number Diff line number Diff line change
@@ -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.
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
"""
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_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 self.query_metrics('device_group', [device_group_id], metrics)
Loading