Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,907 @@
#!/usr/bin/python
# Copyright: (c) 2022, Dell Technologies
# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt)
""" Ansible module for managing replication consistency groups on Dell Technologies (Dell) PowerFlex"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
module: replication_consistency_group
version_added: '1.5.0'
short_description: Manage replication consistency groups on Dell PowerFlex
description:
- Managing replication consistency groups on PowerFlex storage system includes
getting details, creating, modifying, creating snapshots, pause, resume, freeze, unfreeze,
activate, inactivate and deleting a replication consistency group.
author:
- Trisha Datta (@Trisha-Datta) <ansible.team@dell.com>
- Jennifer John (@Jennifer-John) <ansible.team@dell.com>
extends_documentation_fragment:
- dellemc.powerflex.powerflex
options:
rcg_name:
description:
- The name of the replication consistency group.
- It is unique across the PowerFlex array.
- Mutually exclusive with I(rcg_id).
type: str
rcg_id:
description:
- The ID of the replication consistency group.
- Mutually exclusive with I(rcg_name).
type: str
create_snapshot:
description:
- Whether to create the snapshot of the replication consistency group.
type: bool
rpo:
description:
- Desired RPO in seconds.
type: int
protection_domain_id:
description:
- Protection domain id.
- Mutually exclusive with I(protection_domain_name).
type: str
protection_domain_name:
description:
- Protection domain name.
- Mutually exclusive with I(protection_domain_id).
type: str
activity_mode:
description:
- Activity mode of RCG.
- This parameter is supported for version 3.6 and above.
choices: ['Active', 'Inactive']
type: str
pause:
description:
- Pause or resume the RCG.
type: bool
freeze:
description:
- Freeze or unfreeze the RCG.
type: bool
pause_mode:
description:
- Pause mode.
- It is required if pause is set as True.
choices: ['StopDataTransfer', 'OnlyTrackChanges']
type: str
target_volume_access_mode:
description:
- Target volume access mode.
choices: ['ReadOnly', 'NoAccess']
type: str
is_consistent:
description:
- Consistency of RCG.
type: bool
new_rcg_name:
description:
- Name of RCG to rename to.
type: str
remote_peer:
description:
- Remote peer system.
type: dict
suboptions:
hostname:
required: true
description:
- IP or FQDN of the remote peer gateway host.
type: str
aliases:
- gateway_host
username:
type: str
required: true
description:
- The username of the remote peer gateway host.
password:
type: str
required: true
description:
- The password of the remote peer gateway host.
validate_certs:
type: bool
default: true
aliases:
- verifycert
description:
- Boolean variable to specify whether or not to validate SSL
certificate.
- C(true) - Indicates that the SSL certificate should be verified.
- C(false) - Indicates that the SSL certificate should not be verified.
port:
description:
- Port number through which communication happens with remote peer
gateway host.
type: int
default: 443
timeout:
description:
- Time after which connection will get terminated.
- It is to be mentioned in seconds.
type: int
default: 120
protection_domain_id:
description:
- Remote protection domain id.
- Mutually exclusive with I(protection_domain_name).
type: str
protection_domain_name:
description:
- Remote protection domain name.
- Mutually exclusive with I(protection_domain_id).
type: str
state:
description:
- State of the replication consistency group.
choices: ['present', 'absent']
default: present
type: str
notes:
- The I(check_mode) is supported.
- Idempotency is not supported for create snapshot operation.
- There is a delay in reflection of final state of RCG after few update operations on RCG.
- In 3.6 and above, the replication consistency group will return back to consistent mode on changing to inconsistent mode
if consistence barrier arrives. Hence idempotency on setting to inconsistent mode will return changed as True.
'''
EXAMPLES = r'''
- name: Get RCG details
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "{{rcg_name}}"
- name: Create a snapshot of the RCG
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_id: "{{rcg_id}}"
create_snapshot: True
state: "present"
- name: Create a replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
rpo: 60
protection_domain_name: "domain1"
activity_mode: "active"
remote_peer:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
protection_domain_name: "domain1"
- name: Modify replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
rpo: 60
target_volume_access_mode: "ReadOnly"
activity_mode: "Inactive"
is_consistent: True
- name: Rename replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
new_rcg_name: "rcg_test_rename"
- name: Pause replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
action: "pause"
pause_mode: "StopDataTransfer"
- name: Resume replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
action: "resume"
- name: Freeze replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
action: "freeze"
- name: UnFreeze replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
action: "unfreeze"
- name: Delete replication consistency group
dellemc.powerflex.replication_consistency_group:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
rcg_name: "rcg_test"
state: "absent"
'''
RETURN = r'''
changed:
description: Whether or not the resource has changed.
returned: always
type: bool
sample: 'false'
replication_consistency_group_details:
description: Details of the replication consistency group.
returned: When replication consistency group exists
type: dict
contains:
id:
description: The ID of the replication consistency group.
type: str
name:
description: The name of the replication consistency group.
type: str
protectionDomainId:
description: The Protection Domain ID of the replication consistency group.
type: str
peerMdmId:
description: The ID of the peer MDM of the replication consistency group.
type: str
remoteId:
description: The ID of the remote replication consistency group.
type: str
remoteMdmId:
description: The ID of the remote MDM of the replication consistency group.
type: str
currConsistMode:
description: The current consistency mode of the replication consistency group.
type: str
freezeState:
description: The freeze state of the replication consistency group.
type: str
lifetimeState:
description: The Lifetime state of the replication consistency group.
type: str
pauseMode:
description: The Lifetime state of the replication consistency group.
type: str
snapCreationInProgress:
description: Whether the process of snapshot creation of the replication consistency group is in progress or not.
type: bool
lastSnapGroupId:
description: ID of the last snapshot of the replication consistency group.
type: str
lastSnapCreationRc:
description: The return code of the last snapshot of the replication consistency group.
type: int
targetVolumeAccessMode:
description: The access mode of the target volume of the replication consistency group.
type: str
remoteProtectionDomainId:
description: The ID of the remote Protection Domain.
type: str
remoteProtectionDomainName:
description: The Name of the remote Protection Domain.
type: str
failoverType:
description: The type of failover of the replication consistency group.
type: str
failoverState:
description: The state of failover of the replication consistency group.
type: str
activeLocal:
description: Whether the local replication consistency group is active.
type: bool
activeRemote:
description: Whether the remote replication consistency group is active
type: bool
abstractState:
description: The abstract state of the replication consistency group.
type: str
localActivityState:
description: The state of activity of the local replication consistency group.
type: str
remoteActivityState:
description: The state of activity of the remote replication consistency group..
type: str
inactiveReason:
description: The reason for the inactivity of the replication consistency group.
type: int
rpoInSeconds:
description: The RPO value of the replication consistency group in seconds.
type: int
replicationDirection:
description: The direction of the replication of the replication consistency group.
type: str
disasterRecoveryState:
description: The state of disaster recovery of the local replication consistency group.
type: str
remoteDisasterRecoveryState:
description: The state of disaster recovery of the remote replication consistency group.
type: str
error:
description: The error code of the replication consistency group.
type: int
type:
description: The type of the replication consistency group.
type: str
sample: {
"protectionDomainId": "b969400500000000",
"peerMdmId": "6c3d94f600000000",
"remoteId": "2130961a00000000",
"remoteMdmId": "0e7a082862fedf0f",
"currConsistMode": "Consistent",
"freezeState": "Unfrozen",
"lifetimeState": "Normal",
"pauseMode": "None",
"snapCreationInProgress": false,
"lastSnapGroupId": "e58280b300000001",
"lastSnapCreationRc": "SUCCESS",
"targetVolumeAccessMode": "NoAccess",
"remoteProtectionDomainId": "4eeb304600000000",
"remoteProtectionDomainName": "domain1",
"failoverType": "None",
"failoverState": "None",
"activeLocal": true,
"activeRemote": true,
"abstractState": "Ok",
"localActivityState": "Active",
"remoteActivityState": "Active",
"inactiveReason": 11,
"rpoInSeconds": 30,
"replicationDirection": "LocalToRemote",
"disasterRecoveryState": "None",
"remoteDisasterRecoveryState": "None",
"error": 65,
"name": "test_rcg",
"type": "User",
"id": "aadc17d500000000"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \
import utils
LOG = utils.get_logger('replication_consistency_group')
class PowerFlexReplicationConsistencyGroup(object):
"""Class with replication consistency group operations"""
def __init__(self):
""" Define all parameters required by this module"""
self.module_params = utils.get_powerflex_gateway_host_parameters()
self.module_params.update(get_powerflex_replication_consistency_group_parameters())
mut_ex_args = [['rcg_name', 'rcg_id'], ['protection_domain_id', 'protection_domain_name']]
required_one_of_args = [['rcg_name', 'rcg_id']]
# initialize the Ansible module
self.module = AnsibleModule(
argument_spec=self.module_params,
supports_check_mode=True,
mutually_exclusive=mut_ex_args,
required_one_of=required_one_of_args)
utils.ensure_required_libs(self.module)
try:
self.powerflex_conn = utils.get_powerflex_gateway_host_connection(
self.module.params)
LOG.info("Got the PowerFlex system connection object instance")
except Exception as e:
LOG.error(str(e))
self.module.fail_json(msg=str(e))
def get_rcg(self, rcg_name=None, rcg_id=None):
"""Get rcg details
:param rcg_name: Name of the rcg
:param rcg_id: ID of the rcg
:return: RCG details
"""
name_or_id = rcg_id if rcg_id else rcg_name
try:
rcg_details = None
if rcg_id:
rcg_details = self.powerflex_conn.replication_consistency_group.get(
filter_fields={'id': rcg_id})
if rcg_name:
rcg_details = self.powerflex_conn.replication_consistency_group.get(
filter_fields={'name': rcg_name})
if rcg_details:
rcg_details[0]['statistics'] = \
self.powerflex_conn.replication_consistency_group.get_statistics(rcg_details[0]['id'])
rcg_details[0].pop('links', None)
self.append_protection_domain_name(rcg_details[0])
return rcg_details[0]
except Exception as e:
errormsg = "Failed to get the replication consistency group {0} with" \
" error {1}".format(name_or_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def create_rcg_snapshot(self, rcg_id):
"""Create RCG snapshot
:param rcg_id: Unique identifier of the RCG.
:return: Boolean indicating if create snapshot operation is successful
"""
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.create_snapshot(
rcg_id=rcg_id)
return True
except Exception as e:
errormsg = "Create RCG snapshot for RCG with id {0} operation failed with " \
"error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def create_rcg(self, rcg_params):
"""Create RCG"""
try:
resp = None
# Get remote system details
self.remote_powerflex_conn = utils.get_powerflex_gateway_host_connection(
self.module.params['remote_peer'])
LOG.info("Got the remote peer connection object instance")
protection_domain_id = rcg_params['protection_domain_id']
if rcg_params['protection_domain_name']:
protection_domain_id = \
self.get_protection_domain(self.powerflex_conn, rcg_params['protection_domain_name'])['id']
remote_protection_domain_id = rcg_params['remote_peer']['protection_domain_id']
if rcg_params['remote_peer']['protection_domain_name']:
remote_protection_domain_id = \
self.get_protection_domain(self.remote_powerflex_conn,
rcg_params['remote_peer']['protection_domain_name'])['id']
if not self.module.check_mode:
resp = self.powerflex_conn.replication_consistency_group.create(
rpo=rcg_params['rpo'],
protection_domain_id=protection_domain_id,
remote_protection_domain_id=remote_protection_domain_id,
destination_system_id=self.remote_powerflex_conn.system.get()[0]['id'],
name=rcg_params['rcg_name'],
activity_mode=rcg_params['activity_mode'])
return True, resp
except Exception as e:
errormsg = "Create replication consistency group failed with error {0}".format(str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def modify_rpo(self, rcg_id, rpo):
"""Modify rpo
:param rcg_id: Unique identifier of the RCG.
:param rpo: rpo value in seconds
:return: Boolean indicates if modify rpo is successful
"""
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.modify_rpo(
rcg_id, rpo)
return True
except Exception as e:
errormsg = "Modify rpo for replication consistency group {0} failed with " \
"error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def modify_target_volume_access_mode(self, rcg_id, target_volume_access_mode):
"""Modify target volume access mode
:param rcg_id: Unique identifier of the RCG.
:param target_volume_access_mode: Target volume access mode.
:return: Boolean indicates if modify operation is successful
"""
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.modify_target_volume_access_mode(
rcg_id, target_volume_access_mode)
return True
except Exception as e:
errormsg = "Modify target volume access mode for replication consistency group {0} failed with " \
"error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def modify_activity_mode(self, rcg_id, rcg_details, activity_mode):
"""Modify activity mode
:param rcg_id: Unique identifier of the RCG.
:param rcg_details: RCG details.
:param activity_mode: RCG activity mode.
:return: Boolean indicates if modify operation is successful
"""
try:
if activity_mode == 'Active' and rcg_details['localActivityState'].lower() == 'inactive':
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.activate(rcg_id)
return True
elif activity_mode == 'Inactive' and rcg_details['localActivityState'].lower() == 'active':
if not self.module.check_mode:
rcg_details = self.powerflex_conn.replication_consistency_group.inactivate(rcg_id)
return True
except Exception as e:
errormsg = "Modify activity_mode for replication consistency group {0} failed with " \
"error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def pause_or_resume_rcg(self, rcg_id, rcg_details, pause, pause_mode=None):
"""Perform specified rcg action
:param rcg_id: Unique identifier of the RCG.
:param rcg_details: RCG details.
:param pause: Pause or resume RCG.
:param pause_mode: Specifies the pause mode if pause is True.
:return: Boolean indicates if rcg action is successful
"""
if pause and rcg_details['pauseMode'] == 'None':
if not pause_mode:
self.module.fail_json(msg="Specify pause_mode to perform pause on replication consistency group.")
return self.pause(rcg_id, pause_mode)
if not pause and rcg_details['pauseMode'] != 'None':
return self.resume(rcg_id)
def freeze_or_unfreeze_rcg(self, rcg_id, rcg_details, freeze):
"""Perform specified rcg action
:param rcg_id: Unique identifier of the RCG.
:param rcg_details: RCG details.
:param freeze: Freeze or unfreeze RCG.
:return: Boolean indicates if rcg action is successful
"""
if freeze and rcg_details['freezeState'].lower() == 'unfrozen':
return self.freeze(rcg_id)
if not freeze and rcg_details['freezeState'].lower() == 'frozen':
return self.unfreeze(rcg_id)
def freeze(self, rcg_id):
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.freeze(rcg_id)
return True
except Exception as e:
errormsg = "Freeze replication consistency group {0} failed with error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def unfreeze(self, rcg_id):
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.unfreeze(rcg_id)
return True
except Exception as e:
errormsg = "Unfreeze replication consistency group {0} failed with error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def pause(self, rcg_id, pause_mode):
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.pause(rcg_id, pause_mode)
return True
except Exception as e:
errormsg = "Pause replication consistency group {0} failed with error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def resume(self, rcg_id):
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.resume(rcg_id)
return True
except Exception as e:
errormsg = "Resume replication consistency group {0} failed with error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def set_consistency(self, rcg_id, rcg_details, is_consistent):
"""Set rcg to specified mode
:param rcg_id: Unique identifier of the RCG.
:param rcg_details: RCG details.
:param is_consistent: RCG consistency.
:return: Boolean indicates if set consistency is successful
"""
try:
if is_consistent and rcg_details['currConsistMode'].lower() not in ('consistent', 'consistentpending'):
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.set_as_consistent(rcg_id)
return True
elif not is_consistent and rcg_details['currConsistMode'].lower() not in ('inconsistent', 'inconsistentpending'):
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.set_as_inconsistent(rcg_id)
return True
except Exception as e:
errormsg = "Modifying consistency of replication consistency group failed with error {0}".format(str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def rename_rcg(self, rcg_id, rcg_details, new_name):
"""Rename rcg
:param rcg_id: Unique identifier of the RCG.
:param rcg_details: RCG details
:param new_name: RCG name to rename to.
:return: Boolean indicates if rename is successful
"""
try:
if rcg_details['name'] != new_name:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.rename_rcg(rcg_id, new_name)
return True
except Exception as e:
errormsg = "Renaming replication consistency group to {0} failed with error {1}".format(new_name, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def delete_rcg(self, rcg_id):
"""Delete RCG
:param rcg_id: Unique identifier of the RCG.
:return: Boolean indicates if delete rcg operation is successful
"""
try:
if not self.module.check_mode:
self.powerflex_conn.replication_consistency_group.delete(
rcg_id=rcg_id)
return True
except Exception as e:
errormsg = "Delete replication consistency group {0} failed with " \
"error {1}".format(rcg_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def get_protection_domain(self, conn, protection_domain_name=None, protection_domain_id=None):
"""
Get protection domain details
:param conn: local or remote connection
:param protection_domain_name: Name of the protection domain
:param protection_domain_id: ID of the protection domain
:return: Protection domain id if exists
:rtype: str
"""
name_or_id = protection_domain_id if protection_domain_id \
else protection_domain_name
try:
pd_details = []
if protection_domain_id:
pd_details = conn.protection_domain.get(
filter_fields={'id': protection_domain_id})
if protection_domain_name:
pd_details = conn.protection_domain.get(
filter_fields={'name': protection_domain_name})
if len(pd_details) == 0:
error_msg = "Unable to find the protection domain with " \
"'%s'." % name_or_id
self.module.fail_json(msg=error_msg)
return pd_details[0]
except Exception as e:
error_msg = "Failed to get the protection domain '%s' with " \
"error '%s'" % (name_or_id, str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
def validate_create(self, rcg_params):
"""Validate create RCG params"""
params = ['create_snapshot', 'new_rcg_name']
for param in params:
if rcg_params[param] is not None:
self.module.fail_json(msg="%s cannot be specified while creating replication consistency group" % param)
if not rcg_params['rpo']:
self.module.fail_json(msg='Enter rpo to create replication consistency group')
if not rcg_params['remote_peer']:
self.module.fail_json(msg='Enter remote_peer to create replication consistency group')
if not rcg_params['protection_domain_id'] and not rcg_params['protection_domain_name']:
self.module.fail_json(msg='Enter protection_domain_name or protection_domain_id to create replication consistency group')
if (not rcg_params['remote_peer']['protection_domain_id'] and not rcg_params['remote_peer']['protection_domain_name']) or \
(rcg_params['remote_peer']['protection_domain_id'] is not None and
rcg_params['remote_peer']['protection_domain_name'] is not None):
self.module.fail_json(msg='Enter remote protection_domain_name or protection_domain_id to create replication consistency group')
def modify_rcg(self, rcg_id, rcg_details):
create_snapshot = self.module.params['create_snapshot']
rpo = self.module.params['rpo']
target_volume_access_mode = self.module.params['target_volume_access_mode']
pause = self.module.params['pause']
freeze = self.module.params['freeze']
is_consistent = self.module.params['is_consistent']
activity_mode = self.module.params['activity_mode']
new_rcg_name = self.module.params['new_rcg_name']
changed = False
if create_snapshot is True:
changed = self.create_rcg_snapshot(rcg_id)
if rpo and rcg_details['rpoInSeconds'] and \
rpo != rcg_details['rpoInSeconds']:
changed = self.modify_rpo(rcg_id, rpo)
if target_volume_access_mode and \
rcg_details['targetVolumeAccessMode'] != target_volume_access_mode:
changed = \
self.modify_target_volume_access_mode(rcg_id, target_volume_access_mode)
if activity_mode and \
self.modify_activity_mode(rcg_id, rcg_details, activity_mode):
changed = True
rcg_details = self.get_rcg(rcg_id=rcg_details['id'])
if pause is not None and \
self.pause_or_resume_rcg(rcg_id, rcg_details, pause, self.module.params['pause_mode']):
changed = True
if freeze is not None and \
self.freeze_or_unfreeze_rcg(rcg_id, rcg_details, freeze):
changed = True
if is_consistent is not None and \
self.set_consistency(rcg_id, rcg_details, is_consistent):
changed = True
if new_rcg_name and self.rename_rcg(rcg_id, rcg_details, new_rcg_name):
changed = True
return changed
def validate_input(self, rcg_params):
try:
api_version = self.powerflex_conn.system.get()[0]['mdmCluster']['master']['versionInfo']
if rcg_params['activity_mode'] is not None and utils.is_version_less_than_3_6(api_version):
self.module.fail_json(msg='activity_mode is supported only from version 3.6 and above')
params = ['rcg_name', 'new_rcg_name']
for param in params:
if rcg_params[param] and utils.is_invalid_name(rcg_params[param]):
self.module.fail_json(msg='Enter a valid %s' % param)
if rcg_params['pause_mode'] and rcg_params['pause'] is None:
self.module.fail_json(msg='Specify pause as True to pause replication consistency group')
except Exception as e:
error_msg = "Validating input parameters failed with " \
"error '%s'" % (str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
def append_protection_domain_name(self, rcg_details):
try:
# Append protection domain name
if 'protectionDomainId' in rcg_details \
and rcg_details['protectionDomainId']:
pd_details = self.get_protection_domain(
conn=self.powerflex_conn,
protection_domain_id=rcg_details['protectionDomainId'])
rcg_details['protectionDomainName'] = pd_details['name']
except Exception as e:
error_msg = "Updating replication consistency group details with protection domain name failed with " \
"error '%s'" % (str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
def perform_module_operation(self):
"""
Perform different actions on replication consistency group based on parameters passed in
the playbook
"""
self.validate_input(self.module.params)
rcg_name = self.module.params['rcg_name']
new_rcg_name = self.module.params['new_rcg_name']
rcg_id = self.module.params['rcg_id']
state = self.module.params['state']
# result is a dictionary to contain end state and RCG details
changed = False
result = dict(
changed=False,
replication_consistency_group_details=[]
)
# get RCG details
rcg_details = self.get_rcg(rcg_name, rcg_id)
if rcg_details:
result['replication_consistency_group_details'] = rcg_details
rcg_id = rcg_details['id']
msg = "Fetched the RCG details {0}".format(str(rcg_details))
LOG.info(msg)
# perform create
if state == "present":
if not rcg_details:
self.validate_create(self.module.params)
changed, rcg_details = self.create_rcg(self.module.params)
if rcg_details:
rcg_id = rcg_details['id']
if rcg_details and self.modify_rcg(rcg_id, rcg_details):
changed = True
if state == "absent" and rcg_details:
changed = self.delete_rcg(rcg_id=rcg_details['id'])
# Returning the RCG details
if changed:
result['replication_consistency_group_details'] = \
self.get_rcg(new_rcg_name or rcg_name, rcg_id)
result['changed'] = changed
self.module.exit_json(**result)
def get_powerflex_replication_consistency_group_parameters():
"""This method provide parameter required for the replication_consistency_group
module on PowerFlex"""
return dict(
rcg_name=dict(), rcg_id=dict(),
create_snapshot=dict(type='bool'),
rpo=dict(type='int'), protection_domain_id=dict(),
protection_domain_name=dict(), new_rcg_name=dict(),
activity_mode=dict(choices=['Active', 'Inactive']),
pause=dict(type='bool'), freeze=dict(type='bool'),
pause_mode=dict(choices=['StopDataTransfer', 'OnlyTrackChanges']),
target_volume_access_mode=dict(choices=['ReadOnly', 'NoAccess']),
is_consistent=dict(type='bool'),
remote_peer=dict(type='dict',
options=dict(hostname=dict(type='str', aliases=['gateway_host'], required=True),
username=dict(type='str', required=True),
password=dict(type='str', required=True, no_log=True),
validate_certs=dict(type='bool', aliases=['verifycert'], default=True),
port=dict(type='int', default=443),
timeout=dict(type='int', default=120),
protection_domain_id=dict(),
protection_domain_name=dict())),
state=dict(default='present', type='str', choices=['present', 'absent'])
)
def main():
""" Create PowerFlex Replication Consistency Group object and perform actions on it
based on user input from playbook"""
obj = PowerFlexReplicationConsistencyGroup()
obj.perform_module_operation()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,365 @@
#!/usr/bin/python
# Copyright: (c) 2021, Dell Technologies
# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt)
""" Ansible module for managing SDCs on Dell Technologies (Dell) PowerFlex"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
module: sdc
version_added: '1.0.0'
short_description: Manage SDCs on Dell PowerFlex
description:
- Managing SDCs on PowerFlex storage system includes getting details of SDC
and renaming SDC.
author:
- Akash Shendge (@shenda1) <ansible.team@dell.com>
extends_documentation_fragment:
- dellemc.powerflex.powerflex
options:
sdc_name:
description:
- Name of the SDC.
- Specify either I(sdc_name), I(sdc_id) or I(sdc_ip) for get/rename operation.
- Mutually exclusive with I(sdc_id) and I(sdc_ip).
type: str
sdc_id:
description:
- ID of the SDC.
- Specify either I(sdc_name), I(sdc_id) or I(sdc_ip) for get/rename operation.
- Mutually exclusive with I(sdc_name) and I(sdc_ip).
type: str
sdc_ip:
description:
- IP of the SDC.
- Specify either I(sdc_name), I(sdc_id) or I(sdc_ip) for get/rename operation.
- Mutually exclusive with I(sdc_id) and I(sdc_name).
type: str
sdc_new_name:
description:
- New name of the SDC. Used to rename the SDC.
type: str
state:
description:
- State of the SDC.
choices: ['present', 'absent']
required: true
type: str
notes:
- The I(check_mode) is not supported.
'''
EXAMPLES = r'''
- name: Get SDC details using SDC ip
dellemc.powerflex.sdc:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
sdc_ip: "{{sdc_ip}}"
state: "present"
- name: Rename SDC using SDC name
dellemc.powerflex.sdc:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
sdc_name: "centos_sdc"
sdc_new_name: "centos_sdc_renamed"
state: "present"
'''
RETURN = r'''
changed:
description: Whether or not the resource has changed.
returned: always
type: bool
sample: 'false'
sdc_details:
description: Details of the SDC.
returned: When SDC exists
type: dict
contains:
id:
description: The ID of the SDC.
type: str
name:
description: Name of the SDC.
type: str
sdcIp:
description: IP of the SDC.
type: str
osType:
description: OS type of the SDC.
type: str
mapped_volumes:
description: The details of the mapped volumes.
type: list
contains:
id:
description: The ID of the volume.
type: str
name:
description: The name of the volume.
type: str
volumeType:
description: Type of the volume.
type: str
sdcApproved:
description: Indicates whether an SDC has approved access to the
system.
type: bool
sample: {
"id": "07335d3d00000006",
"installedSoftwareVersionInfo": "R3_6.0.0",
"kernelBuildNumber": null,
"kernelVersion": "3.10.0",
"links": [
{
"href": "/api/instances/Sdc::07335d3d00000006",
"rel": "self"
},
{
"href": "/api/instances/Sdc::07335d3d00000006/relationships/
Statistics",
"rel": "/api/Sdc/relationship/Statistics"
},
{
"href": "/api/instances/Sdc::07335d3d00000006/relationships/
Volume",
"rel": "/api/Sdc/relationship/Volume"
},
{
"href": "/api/instances/System::4a54a8ba6df0690f",
"rel": "/api/parent/relationship/systemId"
}
],
"mapped_volumes": [],
"mdmConnectionState": "Disconnected",
"memoryAllocationFailure": null,
"name": "LGLAP203",
"osType": "Linux",
"peerMdmId": null,
"perfProfile": "HighPerformance",
"sdcApproved": true,
"sdcApprovedIps": null,
"sdcGuid": "F8ECB844-23B8-4629-92BB-B6E49A1744CB",
"sdcIp": "N/A",
"sdcIps": null,
"sdcType": "AppSdc",
"sdrId": null,
"socketAllocationFailure": null,
"softwareVersionInfo": "R3_6.0.0",
"systemId": "4a54a8ba6df0690f",
"versionInfo": "R3_6.0.0"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell\
import utils
LOG = utils.get_logger('sdc')
class PowerFlexSdc(object):
"""Class with SDC operations"""
def __init__(self):
""" Define all parameters required by this module"""
self.module_params = utils.get_powerflex_gateway_host_parameters()
self.module_params.update(get_powerflex_sdc_parameters())
mutually_exclusive = [['sdc_id', 'sdc_ip', 'sdc_name']]
required_one_of = [['sdc_id', 'sdc_ip', 'sdc_name']]
# initialize the Ansible module
self.module = AnsibleModule(
argument_spec=self.module_params,
supports_check_mode=False,
mutually_exclusive=mutually_exclusive,
required_one_of=required_one_of)
utils.ensure_required_libs(self.module)
try:
self.powerflex_conn = utils.get_powerflex_gateway_host_connection(
self.module.params)
LOG.info("Got the PowerFlex system connection object instance")
except Exception as e:
LOG.error(str(e))
self.module.fail_json(msg=str(e))
def rename_sdc(self, sdc_id, new_name):
"""Rename SDC
:param sdc_id: The ID of the SDC
:param new_name: The new name of the SDC
:return: Boolean indicating if rename operation is successful
"""
try:
self.powerflex_conn.sdc.rename(sdc_id=sdc_id, name=new_name)
return True
except Exception as e:
errormsg = "Failed to rename SDC %s with error %s" % (sdc_id,
str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def get_mapped_volumes(self, sdc_id):
"""Get volumes mapped to SDC
:param sdc_id: The ID of the SDC
:return: List containing volume details mapped to SDC
"""
try:
resp = self.powerflex_conn.sdc.get_mapped_volumes(sdc_id=sdc_id)
return resp
except Exception as e:
errormsg = "Failed to get the volumes mapped to SDC %s with " \
"error %s" % (sdc_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def get_sdc(self, sdc_name=None, sdc_ip=None, sdc_id=None):
"""Get the SDC Details
:param sdc_name: The name of the SDC
:param sdc_ip: The IP of the SDC
:param sdc_id: The ID of the SDC
:return: The dict containing SDC details
"""
if sdc_name:
id_ip_name = sdc_name
elif sdc_ip:
id_ip_name = sdc_ip
else:
id_ip_name = sdc_id
try:
if sdc_name:
sdc_details = self.powerflex_conn.sdc.get(
filter_fields={'name': sdc_name})
elif sdc_ip:
sdc_details = self.powerflex_conn.sdc.get(
filter_fields={'sdcIp': sdc_ip})
else:
sdc_details = self.powerflex_conn.sdc.get(
filter_fields={'id': sdc_id})
if len(sdc_details) == 0:
error_msg = "Unable to find SDC with identifier %s" \
% id_ip_name
LOG.error(error_msg)
return None
sdc_details[0]['mapped_volumes'] = self.get_mapped_volumes(
sdc_details[0]['id'])
return sdc_details[0]
except Exception as e:
errormsg = "Failed to get the SDC %s with error %s" % (
id_ip_name, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def validate_parameters(self, sdc_name=None, sdc_id=None, sdc_ip=None):
"""Validate the input parameters"""
if all(param is None for param in [sdc_name, sdc_id, sdc_ip]):
self.module.fail_json(msg="Please provide sdc_name/sdc_id/sdc_ip "
"with valid input.")
sdc_identifiers = ['sdc_name', 'sdc_id', 'sdc_ip']
for param in sdc_identifiers:
if self.module.params[param] is not None and \
len(self.module.params[param].strip()) == 0:
error_msg = "Please provide valid %s" % param
self.module.fail_json(msg=error_msg)
def perform_module_operation(self):
"""
Perform different actions on SDC based on parameters passed in
the playbook
"""
sdc_name = self.module.params['sdc_name']
sdc_id = self.module.params['sdc_id']
sdc_ip = self.module.params['sdc_ip']
sdc_new_name = self.module.params['sdc_new_name']
state = self.module.params['state']
# result is a dictionary to contain end state and SDC details
changed = False
result = dict(
changed=False,
sdc_details={}
)
self.validate_parameters(sdc_name, sdc_id, sdc_ip)
sdc_details = self.get_sdc(sdc_name=sdc_name, sdc_id=sdc_id,
sdc_ip=sdc_ip)
if sdc_name:
id_ip_name = sdc_name
elif sdc_ip:
id_ip_name = sdc_ip
else:
id_ip_name = sdc_id
if state == 'present' and not sdc_details:
error_msg = 'Could not find any SDC instance with ' \
'identifier %s.' % id_ip_name
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
if state == 'absent' and sdc_details:
error_msg = 'Removal of SDC is not allowed through Ansible ' \
'module.'
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
if state == 'present' and sdc_details and sdc_new_name is not None:
if len(sdc_new_name.strip()) == 0:
self.module.fail_json(msg="Please provide valid SDC name.")
changed = self.rename_sdc(sdc_details['id'], sdc_new_name)
if changed:
sdc_name = sdc_new_name
if state == 'present':
result['sdc_details'] = self.get_sdc(sdc_name=sdc_name,
sdc_id=sdc_id, sdc_ip=sdc_ip)
result['changed'] = changed
self.module.exit_json(**result)
def get_powerflex_sdc_parameters():
"""This method provide parameter required for the Ansible SDC module on
PowerFlex"""
return dict(
sdc_id=dict(),
sdc_ip=dict(),
sdc_name=dict(),
sdc_new_name=dict(),
state=dict(required=True, type='str', choices=['present', 'absent'])
)
def main():
""" Create PowerFlex SDC object and perform actions on it
based on user input from playbook"""
obj = PowerFlexSdc()
obj.perform_module_operation()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,914 @@
#!/usr/bin/python
# Copyright: (c) 2021, Dell Technologies
# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt)
"""Ansible module for managing Dell Technologies (Dell) PowerFlex storage pool"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: storagepool
version_added: '1.0.0'
short_description: Managing Dell PowerFlex storage pool
description:
- Dell PowerFlex storage pool module includes getting the details of
storage pool, creating a new storage pool, and modifying the attribute of
a storage pool.
extends_documentation_fragment:
- dellemc.powerflex.powerflex
author:
- Arindam Datta (@dattaarindam) <ansible.team@dell.com>
- P Srinivas Rao (@srinivas-rao5) <ansible.team@dell.com>
options:
storage_pool_name:
description:
- The name of the storage pool.
- If more than one storage pool is found with the same name then
protection domain id/name is required to perform the task.
- Mutually exclusive with I(storage_pool_id).
type: str
storage_pool_id:
description:
- The id of the storage pool.
- It is auto generated, hence should not be provided during
creation of a storage pool.
- Mutually exclusive with I(storage_pool_name).
type: str
protection_domain_name:
description:
- The name of the protection domain.
- During creation of a pool, either protection domain name or id must be
mentioned.
- Mutually exclusive with I(protection_domain_id).
type: str
protection_domain_id:
description:
- The id of the protection domain.
- During creation of a pool, either protection domain name or id must
be mentioned.
- Mutually exclusive with I(protection_domain_name).
type: str
media_type:
description:
- Type of devices in the storage pool.
type: str
choices: ['HDD', 'SSD', 'TRANSITIONAL']
storage_pool_new_name:
description:
- New name for the storage pool can be provided.
- This parameter is used for renaming the storage pool.
type: str
use_rfcache:
description:
- Enable/Disable RFcache on a specific storage pool.
type: bool
use_rmcache:
description:
- Enable/Disable RMcache on a specific storage pool.
type: bool
state:
description:
- State of the storage pool.
type: str
choices: ["present", "absent"]
required: true
notes:
- TRANSITIONAL media type is supported only during modification.
- The I(check_mode) is not supported.
'''
EXAMPLES = r'''
- name: Get the details of storage pool by name
dellemc.powerflex.storagepool:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
storage_pool_name: "sample_pool_name"
protection_domain_name: "sample_protection_domain"
state: "present"
- name: Get the details of storage pool by id
dellemc.powerflex.storagepool:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
storage_pool_id: "abcd1234ab12r"
state: "present"
- name: Create a new storage pool by name
dellemc.powerflex.storagepool:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
storage_pool_name: "ansible_test_pool"
protection_domain_id: "1c957da800000000"
media_type: "HDD"
state: "present"
- name: Modify a storage pool by name
dellemc.powerflex.storagepool:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
storage_pool_name: "ansible_test_pool"
protection_domain_id: "1c957da800000000"
use_rmcache: True
use_rfcache: True
state: "present"
- name: Rename storage pool by id
dellemc.powerflex.storagepool:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
storage_pool_id: "abcd1234ab12r"
storage_pool_new_name: "new_ansible_pool"
state: "present"
'''
RETURN = r'''
changed:
description: Whether or not the resource has changed.
returned: always
type: bool
sample: 'false'
storage_pool_details:
description: Details of the storage pool.
returned: When storage pool exists
type: dict
contains:
mediaType:
description: Type of devices in the storage pool.
type: str
useRfcache:
description: Enable/Disable RFcache on a specific storage pool.
type: bool
useRmcache:
description: Enable/Disable RMcache on a specific storage pool.
type: bool
id:
description: ID of the storage pool under protection domain.
type: str
name:
description: Name of the storage pool under protection domain.
type: str
protectionDomainId:
description: ID of the protection domain in which pool resides.
type: str
protectionDomainName:
description: Name of the protection domain in which pool resides.
type: str
"statistics":
description: Statistics details of the storage pool.
type: dict
contains:
"capacityInUseInKb":
description: Total capacity of the storage pool.
type: str
"unusedCapacityInKb":
description: Unused capacity of the storage pool.
type: str
"deviceIds":
description: Device Ids of the storage pool.
type: list
sample: {
"addressSpaceUsage": "Normal",
"addressSpaceUsageType": "DeviceCapacityLimit",
"backgroundScannerBWLimitKBps": 3072,
"backgroundScannerMode": "DataComparison",
"bgScannerCompareErrorAction": "ReportAndFix",
"bgScannerReadErrorAction": "ReportAndFix",
"capacityAlertCriticalThreshold": 90,
"capacityAlertHighThreshold": 80,
"capacityUsageState": "Normal",
"capacityUsageType": "NetCapacity",
"checksumEnabled": false,
"compressionMethod": "Invalid",
"dataLayout": "MediumGranularity",
"externalAccelerationType": "None",
"fglAccpId": null,
"fglExtraCapacity": null,
"fglMaxCompressionRatio": null,
"fglMetadataSizeXx100": null,
"fglNvdimmMetadataAmortizationX100": null,
"fglNvdimmWriteCacheSizeInMb": null,
"fglOverProvisioningFactor": null,
"fglPerfProfile": null,
"fglWriteAtomicitySize": null,
"fragmentationEnabled": true,
"id": "e0d8f6c900000000",
"links": [
{
"href": "/api/instances/StoragePool::e0d8f6c900000000",
"rel": "self"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000
/relationships/Statistics",
"rel": "/api/StoragePool/relationship/Statistics"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000
/relationships/SpSds",
"rel": "/api/StoragePool/relationship/SpSds"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000
/relationships/Volume",
"rel": "/api/StoragePool/relationship/Volume"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000
/relationships/Device",
"rel": "/api/StoragePool/relationship/Device"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000
/relationships/VTree",
"rel": "/api/StoragePool/relationship/VTree"
},
{
"href": "/api/instances/ProtectionDomain::9300c1f900000000",
"rel": "/api/parent/relationship/protectionDomainId"
}
],
"statistics": {
"BackgroundScannedInMB": 3466920,
"activeBckRebuildCapacityInKb": 0,
"activeEnterProtectedMaintenanceModeCapacityInKb": 0,
"aggregateCompressionLevel": "Uncompressed",
"atRestCapacityInKb": 1248256,
"backgroundScanCompareErrorCount": 0,
"backgroundScanFixedCompareErrorCount": 0,
"bckRebuildReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"bckRebuildWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"capacityAvailableForVolumeAllocationInKb": 369098752,
"capacityInUseInKb": 2496512,
"capacityInUseNoOverheadInKb": 2496512,
"capacityLimitInKb": 845783040,
"compressedDataCompressionRatio": 0.0,
"compressionRatio": 1.0,
"currentFglMigrationSizeInKb": 0,
"deviceIds": [
],
"enterProtectedMaintenanceModeCapacityInKb": 0,
"enterProtectedMaintenanceModeReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"enterProtectedMaintenanceModeWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"exitProtectedMaintenanceModeReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"exitProtectedMaintenanceModeWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"exposedCapacityInKb": 0,
"failedCapacityInKb": 0,
"fwdRebuildReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"fwdRebuildWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"inMaintenanceCapacityInKb": 0,
"inMaintenanceVacInKb": 0,
"inUseVacInKb": 184549376,
"inaccessibleCapacityInKb": 0,
"logWrittenBlocksInKb": 0,
"maxCapacityInKb": 845783040,
"migratingVolumeIds": [
],
"migratingVtreeIds": [
],
"movingCapacityInKb": 0,
"netCapacityInUseInKb": 1248256,
"normRebuildCapacityInKb": 0,
"normRebuildReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"normRebuildWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"numOfDeviceAtFaultRebuilds": 0,
"numOfDevices": 3,
"numOfIncomingVtreeMigrations": 0,
"numOfVolumes": 8,
"numOfVolumesInDeletion": 0,
"numOfVtrees": 8,
"overallUsageRatio": 73.92289,
"pendingBckRebuildCapacityInKb": 0,
"pendingEnterProtectedMaintenanceModeCapacityInKb": 0,
"pendingExitProtectedMaintenanceModeCapacityInKb": 0,
"pendingFwdRebuildCapacityInKb": 0,
"pendingMovingCapacityInKb": 0,
"pendingMovingInBckRebuildJobs": 0,
"persistentChecksumBuilderProgress": 100.0,
"persistentChecksumCapacityInKb": 414720,
"primaryReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"primaryReadFromDevBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"primaryReadFromRmcacheBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"primaryVacInKb": 92274688,
"primaryWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"protectedCapacityInKb": 2496512,
"protectedVacInKb": 184549376,
"provisionedAddressesInKb": 2496512,
"rebalanceCapacityInKb": 0,
"rebalanceReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"rebalanceWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"rfacheReadHit": 0,
"rfacheWriteHit": 0,
"rfcacheAvgReadTime": 0,
"rfcacheAvgWriteTime": 0,
"rfcacheIoErrors": 0,
"rfcacheIosOutstanding": 0,
"rfcacheIosSkipped": 0,
"rfcacheReadMiss": 0,
"rmPendingAllocatedInKb": 0,
"rmPendingThickInKb": 0,
"rplJournalCapAllowed": 0,
"rplTotalJournalCap": 0,
"rplUsedJournalCap": 0,
"secondaryReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"secondaryReadFromDevBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"secondaryReadFromRmcacheBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"secondaryVacInKb": 92274688,
"secondaryWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"semiProtectedCapacityInKb": 0,
"semiProtectedVacInKb": 0,
"snapCapacityInUseInKb": 0,
"snapCapacityInUseOccupiedInKb": 0,
"snapshotCapacityInKb": 0,
"spSdsIds": [
"abdfe71b00030001",
"abdce71d00040001",
"abdde71e00050001"
],
"spareCapacityInKb": 84578304,
"targetOtherLatency": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"targetReadLatency": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"targetWriteLatency": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"tempCapacityInKb": 0,
"tempCapacityVacInKb": 0,
"thickCapacityInUseInKb": 0,
"thinAndSnapshotRatio": 73.92289,
"thinCapacityAllocatedInKm": 184549376,
"thinCapacityInUseInKb": 0,
"thinUserDataCapacityInKb": 2496512,
"totalFglMigrationSizeInKb": 0,
"totalReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"totalWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"trimmedUserDataCapacityInKb": 0,
"unreachableUnusedCapacityInKb": 0,
"unusedCapacityInKb": 758708224,
"userDataCapacityInKb": 2496512,
"userDataCapacityNoTrimInKb": 2496512,
"userDataReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"userDataSdcReadLatency": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"userDataSdcTrimLatency": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"userDataSdcWriteLatency": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"userDataTrimBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"userDataWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"volMigrationReadBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"volMigrationWriteBwc": {
"numOccured": 0,
"numSeconds": 0,
"totalWeightInKb": 0
},
"volumeAddressSpaceInKb": 922XXXXX,
"volumeAllocationLimitInKb": 3707XXXXX,
"volumeIds": [
"456afc7900XXXXXXXX"
],
"vtreeAddresSpaceInKb": 92274688,
"vtreeIds": [
"32b1681bXXXXXXXX",
]
},
"mediaType": "HDD",
"name": "pool1",
"numOfParallelRebuildRebalanceJobsPerDevice": 2,
"persistentChecksumBuilderLimitKb": 3072,
"persistentChecksumEnabled": true,
"persistentChecksumState": "Protected",
"persistentChecksumValidateOnRead": false,
"protectedMaintenanceModeIoPriorityAppBwPerDeviceThresholdInKbps": null,
"protectedMaintenanceModeIoPriorityAppIopsPerDeviceThreshold": null,
"protectedMaintenanceModeIoPriorityBwLimitPerDeviceInKbps": 10240,
"protectedMaintenanceModeIoPriorityNumOfConcurrentIosPerDevice": 1,
"protectedMaintenanceModeIoPriorityPolicy": "limitNumOfConcurrentIos",
"protectedMaintenanceModeIoPriorityQuietPeriodInMsec": null,
"protectionDomainId": "9300c1f900000000",
"protectionDomainName": "domain1",
"rebalanceEnabled": true,
"rebalanceIoPriorityAppBwPerDeviceThresholdInKbps": null,
"rebalanceIoPriorityAppIopsPerDeviceThreshold": null,
"rebalanceIoPriorityBwLimitPerDeviceInKbps": 10240,
"rebalanceIoPriorityNumOfConcurrentIosPerDevice": 1,
"rebalanceIoPriorityPolicy": "favorAppIos",
"rebalanceIoPriorityQuietPeriodInMsec": null,
"rebuildEnabled": true,
"rebuildIoPriorityAppBwPerDeviceThresholdInKbps": null,
"rebuildIoPriorityAppIopsPerDeviceThreshold": null,
"rebuildIoPriorityBwLimitPerDeviceInKbps": 10240,
"rebuildIoPriorityNumOfConcurrentIosPerDevice": 1,
"rebuildIoPriorityPolicy": "limitNumOfConcurrentIos",
"rebuildIoPriorityQuietPeriodInMsec": null,
"replicationCapacityMaxRatio": 32,
"rmcacheWriteHandlingMode": "Cached",
"sparePercentage": 10,
"useRfcache": false,
"useRmcache": false,
"vtreeMigrationIoPriorityAppBwPerDeviceThresholdInKbps": null,
"vtreeMigrationIoPriorityAppIopsPerDeviceThreshold": null,
"vtreeMigrationIoPriorityBwLimitPerDeviceInKbps": 10240,
"vtreeMigrationIoPriorityNumOfConcurrentIosPerDevice": 1,
"vtreeMigrationIoPriorityPolicy": "favorAppIos",
"vtreeMigrationIoPriorityQuietPeriodInMsec": null,
"zeroPaddingEnabled": true
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell\
import utils
LOG = utils.get_logger('storagepool')
class PowerFlexStoragePool(object):
"""Class with StoragePool operations"""
def __init__(self):
""" Define all parameters required by this module"""
self.module_params = utils.get_powerflex_gateway_host_parameters()
self.module_params.update(get_powerflex_storagepool_parameters())
""" initialize the ansible module """
mut_ex_args = [['storage_pool_name', 'storage_pool_id'],
['protection_domain_name', 'protection_domain_id'],
['storage_pool_id', 'protection_domain_name'],
['storage_pool_id', 'protection_domain_id']]
required_one_of_args = [['storage_pool_name', 'storage_pool_id']]
self.module = AnsibleModule(argument_spec=self.module_params,
supports_check_mode=False,
mutually_exclusive=mut_ex_args,
required_one_of=required_one_of_args)
utils.ensure_required_libs(self.module)
try:
self.powerflex_conn = utils.get_powerflex_gateway_host_connection(
self.module.params)
LOG.info('Got the PowerFlex system connection object instance')
except Exception as e:
LOG.error(str(e))
self.module.fail_json(msg=str(e))
def get_protection_domain(self, protection_domain_name=None,
protection_domain_id=None):
"""Get protection domain details
:param protection_domain_name: Name of the protection domain
:param protection_domain_id: ID of the protection domain
:return: Protection domain details
"""
name_or_id = protection_domain_id if protection_domain_id \
else protection_domain_name
try:
filter_fields = {}
if protection_domain_id:
filter_fields = {'id': protection_domain_id}
if protection_domain_name:
filter_fields = {'name': protection_domain_name}
pd_details = self.powerflex_conn.protection_domain.get(
filter_fields=filter_fields)
if pd_details:
return pd_details[0]
if not pd_details:
err_msg = "Unable to find the protection domain with {0}. " \
"Please enter a valid protection domain" \
" name/id.".format(name_or_id)
self.module.fail_json(msg=err_msg)
except Exception as e:
errormsg = "Failed to get the protection domain {0} with" \
" error {1}".format(name_or_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def get_storage_pool(self, storage_pool_id=None, storage_pool_name=None,
pd_id=None):
"""Get storage pool details
:param pd_id: ID of the protection domain
:param storage_pool_name: The name of the storage pool
:param storage_pool_id: The storage pool id
:return: Storage pool details
"""
name_or_id = storage_pool_id if storage_pool_id \
else storage_pool_name
try:
filter_fields = {}
if storage_pool_id:
filter_fields = {'id': storage_pool_id}
if storage_pool_name:
filter_fields.update({'name': storage_pool_name})
if pd_id:
filter_fields.update({'protectionDomainId': pd_id})
pool_details = self.powerflex_conn.storage_pool.get(
filter_fields=filter_fields)
if pool_details:
if len(pool_details) > 1:
err_msg = "More than one storage pool found with {0}," \
" Please provide protection domain Name/Id" \
" to fetch the unique" \
" storage pool".format(storage_pool_name)
LOG.error(err_msg)
self.module.fail_json(msg=err_msg)
elif len(pool_details) == 1:
pool_details = pool_details[0]
statistics = self.powerflex_conn.storage_pool.get_statistics(pool_details['id'])
pool_details['statistics'] = statistics if statistics else {}
pd_id = pool_details['protectionDomainId']
pd_name = self.get_protection_domain(
protection_domain_id=pd_id)['name']
# adding protection domain name in the pool details
pool_details['protectionDomainName'] = pd_name
else:
pool_details = None
return pool_details
except Exception as e:
errormsg = "Failed to get the storage pool {0} with error " \
"{1}".format(name_or_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def create_storage_pool(self, pool_name, pd_id, media_type,
use_rfcache=None, use_rmcache=None):
"""
Create a storage pool
:param pool_name: Name of the storage pool
:param pd_id: ID of the storage pool
:param media_type: Type of storage device in the pool
:param use_rfcache: Enable/Disable RFcache on pool
:param use_rmcache: Enable/Disable RMcache on pool
:return: True, if the operation is successful
"""
try:
if media_type == "Transitional":
self.module.fail_json(msg="TRANSITIONAL media type is not"
" supported during creation."
" Please enter a valid media type")
if pd_id is None:
self.module.fail_json(
msg="Please provide protection domain details for "
"creation of a storage pool")
self.powerflex_conn.storage_pool.create(
media_type=media_type,
protection_domain_id=pd_id, name=pool_name,
use_rfcache=use_rfcache, use_rmcache=use_rmcache)
return True
except Exception as e:
errormsg = "Failed to create the storage pool {0} with error " \
"{1}".format(pool_name, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def modify_storage_pool(self, pool_id, modify_dict):
"""
Modify the parameters of the storage pool.
:param modify_dict: Dict containing parameters which are to be
modified
:param pool_id: Id of the pool.
:return: True, if the operation is successful.
"""
try:
if 'new_name' in modify_dict:
self.powerflex_conn.storage_pool.rename(
pool_id, modify_dict['new_name'])
if 'use_rmcache' in modify_dict:
self.powerflex_conn.storage_pool.set_use_rmcache(
pool_id, modify_dict['use_rmcache'])
if 'use_rfcache' in modify_dict:
self.powerflex_conn.storage_pool.set_use_rfcache(
pool_id, modify_dict['use_rfcache'])
if 'media_type' in modify_dict:
self.powerflex_conn.storage_pool.set_media_type(
pool_id, modify_dict['media_type'])
return True
except Exception as e:
err_msg = "Failed to update the storage pool {0} with error " \
"{1}".format(pool_id, str(e))
LOG.error(err_msg)
self.module.fail_json(msg=err_msg)
def verify_params(self, pool_details, pd_name, pd_id):
"""
:param pool_details: Details of the storage pool
:param pd_name: Name of the protection domain
:param pd_id: Id of the protection domain
"""
if pd_id and pd_id != pool_details['protectionDomainId']:
self.module.fail_json(msg="Entered protection domain id does not"
" match with the storage pool's "
"protection domain id. Please enter "
"a correct protection domain id.")
if pd_name and pd_name != pool_details['protectionDomainName']:
self.module.fail_json(msg="Entered protection domain name does"
" not match with the storage pool's "
"protection domain name. Please enter"
" a correct protection domain name.")
def perform_module_operation(self):
""" Perform different actions on Storage Pool based on user input
in the playbook """
pool_name = self.module.params['storage_pool_name']
pool_id = self.module.params['storage_pool_id']
pool_new_name = self.module.params['storage_pool_new_name']
state = self.module.params['state']
pd_name = self.module.params['protection_domain_name']
pd_id = self.module.params['protection_domain_id']
use_rmcache = self.module.params['use_rmcache']
use_rfcache = self.module.params['use_rfcache']
media_type = self.module.params['media_type']
if media_type == "TRANSITIONAL":
media_type = 'Transitional'
result = dict(
storage_pool_details={}
)
changed = False
pd_details = None
if pd_name or pd_id:
pd_details = self.get_protection_domain(
protection_domain_id=pd_id,
protection_domain_name=pd_name)
if pd_details:
pd_id = pd_details['id']
if pool_name is not None and (len(pool_name.strip()) == 0):
self.module.fail_json(
msg="Empty or white spaced string provided in "
"storage_pool_name. Please provide valid storage"
" pool name.")
# Get the details of the storage pool.
pool_details = self.get_storage_pool(storage_pool_id=pool_id,
storage_pool_name=pool_name,
pd_id=pd_id)
if pool_name and pool_details:
pool_id = pool_details['id']
self.verify_params(pool_details, pd_name, pd_id)
# create a storage pool
if state == 'present' and not pool_details:
LOG.info("Creating new storage pool")
if pool_id:
self.module.fail_json(
msg="storage_pool_name is missing & name required to "
"create a storage pool. Please enter a valid "
"storage_pool_name.")
if pool_new_name is not None:
self.module.fail_json(
msg="storage_pool_new_name is passed during creation. "
"storage_pool_new_name is not allowed during "
"creation of a storage pool.")
changed = self.create_storage_pool(
pool_name, pd_id, media_type, use_rfcache, use_rmcache)
if changed:
pool_id = self.get_storage_pool(storage_pool_id=pool_id,
storage_pool_name=pool_name,
pd_id=pd_id)['id']
# modify the storage pool parameters
if state == 'present' and pool_details:
# check if the parameters are to be updated or not
if pool_new_name is not None and len(pool_new_name.strip()) == 0:
self.module.fail_json(
msg="Empty/White spaced name is not allowed during "
"renaming of a storage pool. Please enter a valid "
"storage pool new name.")
modify_dict = to_modify(pool_details, use_rmcache, use_rfcache,
pool_new_name, media_type)
if bool(modify_dict):
LOG.info("Modify attributes of storage pool")
changed = self.modify_storage_pool(pool_id, modify_dict)
# Delete a storage pool
if state == 'absent' and pool_details:
msg = "Deleting storage pool is not supported through" \
" ansible module."
LOG.error(msg)
self.module.fail_json(msg=msg)
# Show the updated storage pool details
if state == 'present':
pool_details = self.get_storage_pool(storage_pool_id=pool_id)
# fetching Id from pool details to address a case where
# protection domain is not passed
pd_id = pool_details['protectionDomainId']
pd_name = self.get_protection_domain(
protection_domain_id=pd_id)['name']
# adding protection domain name in the pool details
pool_details['protectionDomainName'] = pd_name
result['storage_pool_details'] = pool_details
result['changed'] = changed
self.module.exit_json(**result)
def to_modify(pool_details, use_rmcache, use_rfcache, new_name, media_type):
"""
Check whether a parameter is required to be updated.
:param media_type: Type of the media supported by the pool.
:param pool_details: Details of the storage pool
:param use_rmcache: Enable/Disable RMcache on pool
:param use_rfcache: Enable/Disable RFcache on pool
:param new_name: New name for the storage pool
:return: dict, containing parameters to be modified
"""
pool_name = pool_details['name']
pool_use_rfcache = pool_details['useRfcache']
pool_use_rmcache = pool_details['useRmcache']
pool_media_type = pool_details['mediaType']
modify_params = {}
if new_name is not None and pool_name != new_name:
modify_params['new_name'] = new_name
if use_rfcache is not None and pool_use_rfcache != use_rfcache:
modify_params['use_rfcache'] = use_rfcache
if use_rmcache is not None and pool_use_rmcache != use_rmcache:
modify_params['use_rmcache'] = use_rmcache
if media_type is not None and media_type != pool_media_type:
modify_params['media_type'] = media_type
return modify_params
def get_powerflex_storagepool_parameters():
"""This method provides parameters required for the ansible
Storage Pool module on powerflex"""
return dict(
storage_pool_name=dict(required=False, type='str'),
storage_pool_id=dict(required=False, type='str'),
protection_domain_name=dict(required=False, type='str'),
protection_domain_id=dict(required=False, type='str'),
media_type=dict(required=False, type='str',
choices=['HDD', 'SSD', 'TRANSITIONAL']),
use_rfcache=dict(required=False, type='bool'),
use_rmcache=dict(required=False, type='bool'),
storage_pool_new_name=dict(required=False, type='str'),
state=dict(required=True, type='str', choices=['present', 'absent']))
def main():
""" Create PowerFlex Storage Pool object and perform action on it
based on user input from playbook"""
obj = PowerFlexStoragePool()
obj.perform_module_operation()
if __name__ == '__main__':
main()