Skip to content

Commit

Permalink
Merge pull request #10 from dell/release_1.8.0
Browse files Browse the repository at this point in the history
Release 1.8.0 for PyPowerStore Python Library
  • Loading branch information
Jennifer-John committed Sep 22, 2022
2 parents c08b35f + d65d3e7 commit cb47432
Show file tree
Hide file tree
Showing 15 changed files with 360 additions and 9 deletions.
5 changes: 5 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# PyPowerStore Change Log

## Version 1.8.0 - released on 27/09/22
- Added configuration operations include validate create cluster attributes and create cluster.
- Added support for clone, restore, and refresh a volume group.
- Added support for protection_policy_id for NAS server for FHP and above.

## Version 1.7.0 - released on 28/06/22
- Added configuration operations include LDAP domain and LDAP accounts and getting high level facts about the LDAP accounts.

Expand Down
26 changes: 26 additions & 0 deletions ProgrammersGuideExamples/cluster_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,29 @@
# Modify MTU and name of the cluster
updated_cluster_details = CONN.config_mgmt.modify_cluster(cluster_id=cluster[0]['id'], physical_mtu=1500, name="AB-C1234")
print(updated_cluster_details)

# Validate create cluster
cluster = {"name": "test_cluster", "ignore_network_warnings": True}
appliances = [{"link_local_address": "4x.3x.2x.1x"}]
dns_servers = ["4x.3x.2x.1x"]
ntp_servers = ["4x.3x.2x.1x"]
networks = [
{
"type": "Management",
"prefix_length": 24,
"addresses": ["4x.3x.2x.1x", "1xx.2xx.3xx.4xx"]
}
]
is_http_redirect_enabled = True
validate_resp = CONN.config_mgmt.cluster_create_validate(
cluster=cluster, appliances=appliances, dns_servers=dns_servers,
ntp_servers=ntp_servers, networks=networks,
is_http_redirect_enabled=is_http_redirect_enabled)
print(validate_resp)

# Create Cluster
Create_resp = CONN.config_mgmt.cluster_create(
cluster=cluster, appliances=appliances, dns_servers=dns_servers,
ntp_servers=ntp_servers, networks=networks,
is_http_redirect_enabled=is_http_redirect_enabled)
print(Create_resp)
3 changes: 2 additions & 1 deletion ProgrammersGuideExamples/nas_server_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

MODIFY_PARAMS = {
'description': 'My Description',
'current_unix_directory_service': 'Local_Files'
'current_unix_directory_service': 'Local_Files',
'protection_policy_id': 'samplepolicyid'
}

# Get nasserver list
Expand Down
2 changes: 1 addition & 1 deletion PyPowerStore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"""__init__.py."""

__title__ = 'PyPowerStore'
__version__ = '1.7.0.0'
__version__ = '1.8.0.0'
__author__ = 'Dell Technologies or its subsidiaries'
__copyright__ = 'Copyright 2019 Dell Technologies'
103 changes: 103 additions & 0 deletions PyPowerStore/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,90 @@ def modify_cluster(self, cluster_id, physical_mtu=None, name=None):
)
return self.get_cluster_details(cluster_id)

def cluster_create_validate(self, cluster, appliances, dns_servers,
ntp_servers, networks,
is_http_redirect_enabled,
physical_switches=None, vcenters=None):
"""Validate the creation of cluster configuration
:param cluster: Dict containing parameters for the cluster
:type cluster: dict
:param appliances: List containing appliances
:type appliances: list[dict]
:param dns_servers: List containing IP addresses for DNS Servers
:type dns_servers: list[str]
:param ntp_servers: List containing IP addresses for NTP Servers
:type ntp_servers: list[dict]
:param physical_switches: List containing physical switches
:type physical_switches: list[dict]
:param networks: List containing networks
:type networks: list[dict]
:param vcenters: List of vCenters
:type vcenters: list[dict]
:param is_http_redirect_enabled: Whether to redirect the http requests
to https
:type is_http_redirect_enabled: bool
:return: None
:rtype: None
"""
LOG.info("Validating the create cluster configuration")
cluster_url = constants.CREATE_CLUSTER_VALIDATE_URL

cluster_payload = self.\
_prepare_create_cluster_payload(is_http_redirect_enabled=is_http_redirect_enabled,
cluster=cluster,
appliances=appliances,
dns_servers=dns_servers,
ntp_servers=ntp_servers,
physical_switches=physical_switches,
networks=networks,
vcenters=vcenters)
return self.config_client.request(constants.POST,
cluster_url.format(self.server_ip),
payload=cluster_payload)

def cluster_create(self, cluster, appliances, dns_servers, ntp_servers,
networks, is_http_redirect_enabled,
physical_switches=None, vcenters=None, is_async=False):
"""Create a Cluster of one or more appliances
:param cluster: Dict containing parameters for the cluster
:type cluster: dict
:param appliances: List containing appliances
:type appliances: list[dict]
:param dns_servers: List containing IP addresses for DNS Servers
:type dns_servers: list[str]
:param ntp_servers: List containing IP addresses for NTP Servers
:type ntp_servers: list[dict]
:param physical_switches: List containing physical switches
:type physical_switches: list[dict]
:param networks: List containing networks
:type networks: list[dict]
:param vcenters: List of vCenters
:type vcenters: list[dict]
:param is_http_redirect_enabled: Whether to redirect the http requests
to https
:type is_http_redirect_enabled: bool
:param is_async: Flag to indicate sync/async operation
:type is_async: bool
:return: Unique identifier of the new instance created
:rtype: str
"""
LOG.info("Creating new cluster configuration")
cluster_url = constants.CREATE_CLUSTER_URL
if is_async:
cluster_url = cluster_url + "?is_async=true"
cluster_payload = self.\
_prepare_create_cluster_payload(is_http_redirect_enabled=is_http_redirect_enabled,
cluster=cluster,
appliances=appliances,
dns_servers=dns_servers,
ntp_servers=ntp_servers,
physical_switches=physical_switches,
networks=networks,
vcenters=vcenters)
return self.config_client.request(constants.POST,
cluster_url.format(self.server_ip),
payload=cluster_payload)

# Cluster operations end

# CHAP config operations start
Expand Down Expand Up @@ -1988,3 +2072,22 @@ def _prepare_add_remove_network_payload(**kwargs):
if kwargs.get(argname) is not None:
payload[argname] = kwargs[argname]
return payload

@staticmethod
def _prepare_create_cluster_payload(is_http_redirect_enabled, **kwargs):
"""Prepare payload for creation of cluster
:return: Request body
:rtype: dict
"""
payload = dict()
for agrname in ('cluster', 'appliances', 'dns_servers', 'ntp_servers',
'physical_switches', 'networks', 'vcenters'):
if kwargs.get(agrname) is not None:
payload[agrname] = kwargs[agrname]

if is_http_redirect_enabled is not None:
security_config = dict()
security_config['is_http_redirect_enabled'] = is_http_redirect_enabled
payload['security_config'] = security_config

return payload
111 changes: 109 additions & 2 deletions PyPowerStore/provisioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,104 @@ def create_volume_group(self, name, description=None,
constants.CREATE_VOLUME_GROUP_URL.format(
self.server_ip), payload=payload)

def clone_volume_group(self, volume_group_id, name, description=None, protection_policy_id=None):
"""Clone a volume group.
:param volume_group_id: ID of the volume group to clone
:type volume_group_id: str
:param name: Unique name for the 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
:type protection_policy_id: str
:return: Unique identifier of the new instance created 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)
return self.client.request(
constants.POST,
constants.CLONE_VOLUME_GROUP_URL.format(self.server_ip,
volume_group_id),
payload=payload)

def refresh_volume_group(self, volume_group_id, src_vol_group,
create_backup_snap=None, backup_snap_profile=None):
"""Refresh a volume group.
:param volume_group_id: ID of the volume group to refresh
:type volume_group_id: str
:param src_vol_group: Unique identifier of the volume group to refresh from.
:type src_vol_group: str
:param create_backup_snap: (optional) specifies whether a backup snapshot set of the
target volume group needs to be created before refreshing it.
:type create_backup_snap: bool
:param backup_snap_profile: (optional) Backup profile of the snapshot set to be created.
:type backup_snap_profile: dict
:return: Unique identifier of the backup snapshot set or None if create_backup_snap is None
if success else raise exception
:rtype: dict
"""
LOG.info("Refreshing volumegroup: '%s'" % volume_group_id)
payload = self._prepare_vg_payload('refresh', src_vol_group, create_backup_snap,
backup_snap_profile)
return self.client.request(
constants.POST,
constants.REFRESH_VOLUME_GROUP_URL.format(self.server_ip,
volume_group_id),
payload=payload)

def restore_volume_group(self, volume_group_id, src_snap_id,
create_backup_snap=None, backup_snap_profile=None):
"""Restore a volume group.
:param volume_group_id: ID of the volume group to restore
:type volume_group_id: str
:param src_snap_id: Unique identifier of the snapshot set to restore from.
:type src_snap_id: str
:param create_backup_snap: (optional) specifies whether a backup snapshot set of the
target volume group needs to be created before restore.
:type create_backup_snap: bool
:param backup_snap_profile: (optional) Backup profile of the snapshot set to be created.
:type backup_snap_profile: dict
:return: Unique identifier of the backup snapshot set or None if create_backup_snap is None
if success else raise exception
:rtype: dict
"""
LOG.info("Restoring volumegroup: '%s'" % volume_group_id)
payload = self._prepare_vg_payload('restore', src_snap_id, create_backup_snap, backup_snap_profile)
return self.client.request(
constants.POST,
constants.RESTORE_VOLUME_GROUP_URL.format(self.server_ip,
volume_group_id),
payload=payload)

def _prepare_clone_vg_payload(self, name, description, protection_policy_id):
vol_group_clone = dict()
if name is not None:
vol_group_clone['name'] = name
if description is not None:
vol_group_clone['description'] = description
if protection_policy_id is not None:
vol_group_clone['protection_policy_id'] = protection_policy_id
return vol_group_clone

def _prepare_vg_payload(self, action, src_vol_group, create_backup_snap,
backup_snap_profile):
vg_payload = dict()
if action == 'refresh':
vg_payload['from_object_id'] = src_vol_group
else:
vg_payload['from_snap_id'] = src_vol_group
if create_backup_snap is not None:
vg_payload['create_backup_snap'] = create_backup_snap
if backup_snap_profile:
vg_payload['backup_snap_profile'] = backup_snap_profile

return vg_payload

def _prepare_create_vg_payload(self, name, description, volume_ids,
is_write_order_consistent,
protection_policy_id):
Expand Down Expand Up @@ -1116,6 +1214,7 @@ def get_host_volume_mapping(self, volume_id):
)

# NAS Server methods

def get_nas_servers(self, filter_dict=None, all_pages=False):
"""Get a list of nas servers.
Expand Down Expand Up @@ -1146,13 +1245,17 @@ def get_nas_server_details(self, nas_server_id):
:return: NAS server details
:rtype: dict
"""
querystring = constants.SELECT_ALL_NAS_SERVER
if helpers.is_foot_hill_prime_or_higher():
querystring = constants.FHP_NAS_QUERYSTRING

LOG.info("Getting nasserver details by ID: '%s'" % nas_server_id)
return self.client.request(
constants.GET,
constants.GET_NAS_SERVER_DETAILS_URL.format(self.server_ip,
nas_server_id),
payload=None,
querystring=constants.SELECT_ALL_NAS_SERVER)
querystring=querystring)

def get_nas_server_by_name(self, nas_server_name):
"""Get details of a NAS Server by name.
Expand All @@ -1162,13 +1265,17 @@ def get_nas_server_by_name(self, nas_server_name):
:return: NAS server details
:rtype: dict
"""
querystring = constants.SELECT_ALL_NAS_SERVER
if helpers.is_foot_hill_prime_or_higher():
querystring = constants.FHP_NAS_QUERYSTRING

LOG.info("Getting nasserver details by name: '%s'" % nas_server_name)
return self.client.request(
constants.GET,
constants.GET_NAS_SERVER_DETAILS_BY_NAME_URL.format(
self.server_ip),
payload=None, querystring=helpers.prepare_querystring(
constants.SELECT_ALL_NAS_SERVER,
querystring,
name=constants.EQUALS + nas_server_name
)
)
Expand Down
23 changes: 21 additions & 2 deletions PyPowerStore/tests/unit_tests/data/common_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class CommonData(object):
vg_id2 = "007a5fad-7520-4f2a-a364-6c243d8d4ecf"
vg_name2 = "my_vg2"

create_snapshot = True
snapshot_id = "008a5fad-7520-4f2a-a364-6c243d8d4ecf"
backup_snapshot_profile = {'name' : 'backup_snapshot_name', 'description': '',
'expiration_timestamp': '2023-01-01 00:00:00'}

volumegroup_list = [{"id": vg_id1, "name": vg_name1},
{"id": vg_id2, "name": vg_name2}]

Expand Down Expand Up @@ -324,15 +329,16 @@ class CommonData(object):
'operational_status': 'Started',
'operational_status_l10n': 'Started',
'production_IPv4_interface_id': None,
'production_IPv6_interface_id': None}
'production_IPv6_interface_id': None,
'protection_policy_id': 'samplepolicyid'}

nas_valid_param_list = [
'name', 'description', 'current_node_id', 'preferred_node_id',
'current_unix_directory_service', 'default_unix_user',
'default_windows_user', 'is_username_translation_enabled',
'is_auto_user_mapping_enabled', 'production_IPv4_interface_id',
'production_IPv6_interface_id', 'backup_IPv4_interface_id',
'backup_IPv6_interface_id']
'backup_IPv6_interface_id', 'protection_policy_id']

nas_id_not_exist = "5f4a3017-0bad-899e-e1eb-c6f547282e66"
nas_error = {
Expand Down Expand Up @@ -1062,6 +1068,19 @@ class CommonData(object):
"compatibility_level": 10,
"state_l10n": "Configured"
}
cluster = {"name": "test_cluster", "ignore_network_warnings": True}
appliances = [{"link_local_address": "4x.3x.2x.1x"}]
dns_servers = ["4x.3x.2x.1x"]
ntp_servers = ["4x.3x.2x.1x"]
networks = [
{
"type": "Management",
"prefix_length": 24,
"addresses": ["4x.3x.2x.1x", "1xx.2xx.3xx.4xx"]
}
]
is_http_redirect_enabled = True

invalid_cluster_id = '10'
cluster_error = {
404: {
Expand Down
11 changes: 11 additions & 0 deletions PyPowerStore/tests/unit_tests/entity/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ def get_api_name(self):
return self.get_cluster_details
elif self.method == 'PATCH':
return self.modify_cluster
elif self.method == 'POST':
if self.url.endswith('/validate_create'):
return self.cluster_create_validate
else:
return self.cluster_create

def execute_api(self, api_name):
status_code, response = api_name()
Expand All @@ -42,3 +47,9 @@ def get_cluster_by_name(self):
def modify_cluster(self):
return 204, self.data.cluster_details_1

def cluster_create(self):
return 201, self.data.cluster_id_1

def cluster_create_validate(self):
return 204, None

Loading

0 comments on commit cb47432

Please sign in to comment.